For agents
The whole reference, as one Markdown file.
749 endpoints across 8 surfaces, the 9 MCP tools, and every guide, generated from the same sources as this site. Paste it into a coding agent, or point an MCP server at https://developers.keystone.app/llms-full.txt. About 1351 KB.
# Keystone Developers
Reference for every endpoint in the Keystone platform's API contracts and for the Keystone MCP server.
This file is the complete developer reference as one Markdown document, generated from the platform's OpenAPI contracts and the MCP server's tool registry. Canonical HTML: https://developers.keystone.app. Generated 2026-10-10.
## Contents
- Guides
- Public API (42 endpoints)
- Edits API (14 endpoints)
- Console API (434 endpoints)
- Agent API (46 endpoints)
- Internal API (157 endpoints)
- Webhooks and callbacks (7 endpoints)
- System (4 endpoints)
- Auth service (45 endpoints)
- Keystone MCP server
## Hosts
- SOR (business data): https://sor.keystone.app
- Auth (Heimdal): https://auth.keystone.app
- MCP: https://mcp.keystone.app/mcp
# Guides
## Conventions
What every Keystone endpoint has in common: one envelope, one error shape, UUID ids, and a surface per audience.
HTML: https://developers.keystone.app/guides/conventions/overview/
Keystone's HTTP API is one FastAPI service (the Statement of Record, "SOR") plus a separate auth service (Heimdal). The two publish OpenAPI 3.1 documents, and this site is generated from them. A few conventions hold across every endpoint; the pages in this group describe them so the [reference](https://developers.keystone.app/api/) can stay terse.
### In one paragraph
Requests are JSON over HTTPS. Every successful response is wrapped in a `SuccessResponse` envelope with a `request_id`, `success: true`, and the payload under `data`; every error is an `ErrorResponse` with the same `request_id`, `success: false`, and an `error` object with a `code` and `message`. Identifiers are UUIDs. Paths are versioned at `/api/v1/`. Endpoints are organized into **surfaces** by who calls them and what credential they carry; see [Surfaces](https://developers.keystone.app/guides/conventions/surfaces/).
### Where to look
| Question | Page |
| --- | --- |
| Which endpoints can my site or app call? | [Public API](https://developers.keystone.app/api/public/) and [Edits API](https://developers.keystone.app/api/edits/) |
| What does the console use? | [Console API](https://developers.keystone.app/api/console/) |
| How does an assistant edit a business? | [MCP server](https://developers.keystone.app/mcp/) and [How a session works](https://developers.keystone.app/guides/mcp/sessions/) |
| What does a response look like? | [Envelopes](https://developers.keystone.app/guides/conventions/envelopes/) |
| What does a failure look like? | [Errors](https://developers.keystone.app/guides/conventions/errors/) |
> **Scope of this site**
>
This is a reference: what exists and what it takes and returns. Onboarding, credentials, and account setup are handled with Keystone directly and are not documented here.
## Surfaces
The eight groups of endpoints, by audience and credential, and how they map onto the two services.
HTML: https://developers.keystone.app/guides/conventions/surfaces/
The raw OpenAPI documents list several hundred paths with little structure beyond tags. This site assigns every operation to a **surface** by its path prefix. A surface has one audience and one credential, so once you know which surface you are on you know what every request carries.
### SOR surfaces
| Surface | Prefix | Audience | Credential |
| --- | --- | --- | --- |
| [Public API](https://developers.keystone.app/api/public/) | `/api/v1/public` | A business's website or headless front end | `X-API-Key` (the site's key) |
| [Edits API](https://developers.keystone.app/api/edits/) | `/api/v1/edits` | Assistants and tools making reviewed changes | Console bearer token, or MCP bearer token with `X-Internal-Api-Key` |
| [Console API](https://developers.keystone.app/api/console/) | `/api/v1/admin`, `/api/v1/businesses`, `/api/v1/ai` | The Keystone console | Console bearer token |
| [Agent API](https://developers.keystone.app/api/agent/) | `/api/v1/agent` | Keystone's own agents | `X-Internal-Api-Key` |
| [Internal API](https://developers.keystone.app/api/internal/) | `/api/v1/internal` | Other Keystone services | `X-Internal-Api-Key` |
| [Webhooks and callbacks](https://developers.keystone.app/api/webhooks/) | `/api/v1/webhooks`, `/api/v1/ads/webhook`, `/api/v1/voice` | GitHub, Entri, Meta, the voice provider | The provider's signature |
| [System](https://developers.keystone.app/api/system/) | `/health`, `/api/health`, `/api/v1/sites` | Probes and the sites registry | None |
### Heimdal
| Surface | Prefix | Audience | Credential |
| --- | --- | --- | --- |
| [Auth service](https://developers.keystone.app/api/auth/) | `/api/v1/auth`, `/users`, `/roles`, `/permissions`, `/oauth`, `/consumer`, `/internal` | Console, websites, MCP clients, services | Credentials on sign-in; bearer tokens after; `X-Internal-Api-Key` for `internal` |
### Groups
Inside a surface, operations are grouped by the resource they manage: the spec's resource tag where FastAPI emitted one (`contacts`, `website_mod`, `ads_campaigns`), otherwise the first path segment after the prefix. Group pages carry every operation in full; surface pages list them one per line.
### Why the console surface is so large
The Console API holds everything `go.keystone.app` does, for every area of the product. Most of it is per-business and scoped by the caller's memberships. Platform-staff endpoints (accounts, platform admins, promos, pricing) sit alongside and refuse non-staff callers.
> **If you are building on Keystone**
>
You almost certainly want the Public API for reading and the Edits API (directly or through MCP) for writing. The rest is here so the whole contract is visible, and so internal teams share one reference.
## Envelopes
The SuccessResponse wrapper every 2xx carries, and the metadata it can include.
HTML: https://developers.keystone.app/guides/conventions/envelopes/
Every successful JSON response from SOR is wrapped:
```json
{
"request_id": "7f9d1c2e-...",
"success": true,
"data": { ... },
"metadata": null,
"meta": null
}
```
| Field | Type | Notes |
| --- | --- | --- |
| `request_id` | string | Unique per request. Quote it when reporting a problem. |
| `success` | `true` | Always true on this envelope. Errors use a different shape; see [Errors](https://developers.keystone.app/guides/conventions/errors/). |
| `data` | object or array | The payload. Its schema is the `SuccessResponse__` type on the endpoint, where `` is the inner model. |
| `metadata` | `ResponseMetadata` or null | Timing and pagination where an endpoint pages. |
| `meta` | `ResponseMeta` or null | Endpoint-specific extras, such as the business's time zone or a freshness marker. |
### Reading the reference
On an endpoint's page the 200 response is labelled with the envelope type, for example `SuccessResponse_PublicService_`. Expand it to see `data` and its fields. The inner model is what you will work with; the envelope is the same everywhere.
### Lists
List endpoints return `data` as an array. Where a list pages, `metadata` carries the page and total; the query parameters that drive it are on the endpoint (`page`, `limit`, `offset`, or a cursor, depending on the resource).
### Non-JSON responses
A few endpoints return a file (a sitemap, an `llms.txt`, a media download) or a server-sent event stream. They are marked by their content type in the reference and carry no envelope.
### Heimdal
The auth service returns plain JSON bodies without the SOR envelope. Each endpoint's response schema is shown as-is.
## Errors
The ErrorResponse shape, the status codes you will see, and what the codes mean.
HTML: https://developers.keystone.app/guides/conventions/errors/
Every error from SOR has one shape:
```json
{
"request_id": "7f9d1c2e-...",
"success": false,
"error": {
"code": "not_found",
"message": "Service not found",
"details": null
}
}
```
| Field | Notes |
| --- | --- |
| `request_id` | The same id a success would carry. |
| `error.code` | A stable, machine-readable string. |
| `error.message` | Human-readable. May change; do not match on it. |
| `error.details` | Optional structured context: validation errors by field, or, on the Edits API, the refused items by index. |
### Status codes
| Status | When |
| --- | --- |
| `400` | The request is malformed in a way validation could not express. |
| `401` | Missing or invalid credential for the surface. |
| `403` | Valid credential, but not allowed: wrong business, wrong role, or a policy that refuses. |
| `404` | No such resource, or a feature that is off for this business. |
| `409` | Conflict: compare-and-swap failed, a claim is held by someone else, a slug is taken. |
| `422` | Validation error. `details` lists the fields. |
| `429` | Rate limited. Retry after the interval in `Retry-After`. |
| `500` | Server error. Quote the `request_id`. |
Every endpoint in the reference lists the codes it can return, all with the `ErrorResponse` body.
### On the Edits API
A staged changeset can be partly refused: items that fail validation or compare-and-swap are listed in `error.details.items` with their `index`, `entity`, `code`, and `message`, so a caller can fix only those. The [MCP server](https://developers.keystone.app/mcp/) passes this through to the model verbatim.
## Identifiers and versions
UUIDs everywhere, slugs for public URLs, and record versions for safe writes.
HTML: https://developers.keystone.app/guides/conventions/identifiers/
### Ids
Every record has a UUID `id`. Businesses, locations, services, service items, packages, offers, team members, FAQs, job postings, contacts, conversations, posts, campaigns: all UUIDs, all stable for the life of the record.
Path parameters named `business_id`, `service_id`, and so on take these. The reference shows them as `string (uuid)`.
### Slugs
Public, URL-facing records (services, packages, blog posts) also carry a `slug`. The Public API offers `by_slug` lookups so a site can resolve a URL without knowing the id. Slugs are unique within a business and may be changed by the business; treat the id as the identity and the slug as a label.
### Versions
Records that can be edited through the [Edits API](https://developers.keystone.app/api/edits/) carry a `version` string. Send it back, or the `expected` values you read, when proposing a change; a write against a stale version is refused with `409` rather than silently overwriting. The [MCP server](https://developers.keystone.app/mcp/) does this for you when the model includes `expected` or `version` on a change item.
### Time
Timestamps are ISO 8601 in UTC. Hours of operation and scheduling use the business's `scheduling_timezone` from its profile.
## Connecting an MCP client
The Keystone MCP server speaks standard Streamable HTTP and OAuth 2.1. Anything that can connect to an MCP server can connect to it.
HTML: https://developers.keystone.app/guides/mcp/connect/
The server is not built for one assistant. It implements the Model Context Protocol over Streamable HTTP, authenticates with OAuth 2.1 the way the protocol specifies, and publishes the discovery documents a client needs to find the authorization server on its own. Claude, Cursor, ChatGPT, the MCP Inspector, and anything you write against an MCP SDK connect the same way. The [Keystone Claude Connector](https://developers.keystone.app/guides/mcp/claude/) page covers the one client with a packaged setup.
### What a client needs
| | |
| --- | --- |
| Server URL | `https://mcp.keystone.app/mcp` |
| Transport | Streamable HTTP (the 2026-07-28 protocol; 2025-era clients are served too) |
| Authentication | OAuth 2.1 with PKCE; bearer token in the `Authorization` header |
| Scope | `edits` |
| Discovery | `/.well-known/oauth-protected-resource` (RFC 9728) on the server's host; the authorization server's metadata is mirrored at `/.well-known/oauth-authorization-server` for older clients |
### The sign-in
1. The client calls the server without a token and receives `401` with a `WWW-Authenticate` header pointing at the protected-resource metadata.
2. From that document it learns the authorization server (the Keystone [Auth service](https://developers.keystone.app/api/auth/)) and the `edits` scope, and starts an authorization-code flow with PKCE.
3. The user signs in to Keystone and approves the request. The Auth service issues an MCP access token bound to this server as its audience.
4. The client sends the token on every call. The server verifies the signature, issuer, audience, and token type, then forwards the call to the [Edits API](https://developers.keystone.app/api/edits/) with that token and its own service key.
### Who may connect
Two things are registered on the Keystone side, not by the client:
- **The redirect URI.** The Auth service only redirects to URIs it knows. Claude's and Cursor's are registered; for another client, or your own, send its callback URI to <a href="mailto:developers@keystone.app">developer support</a>.
- **The user.** MCP access is granted per Keystone user. A sign-in from a user who has not been granted it fails at the Auth service with a reason.
Everything after sign-in is governed by SOR, exactly as in the console: who may edit which business, the claim, the launch state, and compare-and-swap on every field.
### Cards
The server ships an MCP App (the card the user reviews changes on). A client that renders MCP Apps shows it; a client that does not still gets every tool's text result and `structuredContent`, and the four card-only tools stay out of the model's reach either way. [Cards and card tokens](https://developers.keystone.app/guides/mcp/cards/) explains why.
> **Trying it without an assistant**
>
The MCP Inspector connects from the browser: paste the server URL, choose Streamable HTTP, and complete the OAuth flow in the popup. The server allows cross-origin calls to `/mcp` for exactly this.
## Keystone Claude Connector
Adding the Keystone MCP server to Claude as a custom connector, for an organization or for one account.
HTML: https://developers.keystone.app/guides/mcp/claude/
Claude connects to the Keystone MCP server as a **custom connector**. Nothing about the server is specific to Claude ([Connecting an MCP client](https://developers.keystone.app/guides/mcp/connect/) is the general case); this page is the Claude-side setup.
### For an organization
1. An organization owner opens **Organization settings → Connectors** and chooses **Add custom connector**.
2. Name it **Keystone** and enter the URL `https://mcp.keystone.app/mcp`. Leave the OAuth client fields empty; Claude discovers the authorization server from the URL.
3. Save. The connector appears for members; each member connects it from their own settings and signs in to Keystone once.
### For one account
In **Settings → Connectors**, add a custom connector with the same URL, then **Connect** and sign in to Keystone.
### Using it
In a conversation, enable the Keystone connector and ask for a business by name, website, or domain. Claude calls `find_business`; the picker card lists the matches. Click one to open a session. From there Claude reads the business and proposes changes; you review them on the card and apply with a click. [How a session works](https://developers.keystone.app/guides/mcp/sessions/) walks the whole sequence, and the [tool reference](https://developers.keystone.app/mcp/) lists what Claude can and cannot call.
> **If the connection fails**
>
The sign-in finishes at Keystone, not at Claude. A refusal there means the user has not been granted MCP access yet; ask <a href="mailto:developers@keystone.app">developer support</a>. Claude's callback URI is already registered with the Auth service, so a redirect error is not something to fix on the Claude side.
## How a session works
Find, open, snapshot, propose, apply: the life of an edit through the MCP server.
HTML: https://developers.keystone.app/guides/mcp/sessions/
The Keystone MCP server lets an assistant change a business's information without ever being able to change it alone. Every write passes through a human click on a card. This page walks the sequence; the [tool reference](https://developers.keystone.app/mcp/) has each tool's schema.
### The sequence
1. **Find.** The model calls `find_business` with a name, website, or domain. The server lists matches and shows a picker card. Each match is **editable** (the user holds a claim on it and its site is not live) or **locked** with a reason.
2. **Open.** The user clicks a business on the card. The card calls `open_session` with a card token minted for that exact click; the server opens an edit session on the Edits API and returns the `session_id` to the model. The model cannot do this step.
3. **Read.** The model calls `get_snapshot` with the session id: the profile and every record with its id, label, and `version`. `read_business` returns one record in full with its write field names.
4. **Propose.** The model calls `propose_changes` with up to 50 change items, each carrying the `expected` values or `version` it read. The server stages a changeset; nothing is saved. The card shows each change, ticked, naming the business.
5. **Apply.** The user unticks what they do not want and clicks Apply. The card calls `apply_changeset` with its token; SOR applies each item with compare-and-swap and writes it to Change History.
6. **Undo, if needed.** The card offers Undo; `undo_changeset` reverts the applied items. A value changed again since the apply is kept.
### Claims
A business can be edited only while the user **holds a claim** on it (24 hours, extendable) and its website is not live. A business nobody holds, and that has not launched, shows **Claim** on the picker; `claim_business` takes it. Live businesses are edited in the console, where Change History and Undo work the same way.
### Sessions expire
A session has an expiry returned by `open_session`. After it, `get_snapshot` and `propose_changes` fail with a reason; find and open again.
### Refusals
Anything SOR refuses (not claimed, launched, conflict, validation) comes back as the tool's text result with the code, and for a changeset, each refused item by position. The model is expected to fix and resend only those.
> **Idempotency**
>
`propose_changes` derives an idempotency key from the session and the items, so a retried call stages the same changeset once.
## Cards and card tokens
Why some tools can only be called by the card, and how card tokens bind a click to an action.
HTML: https://developers.keystone.app/guides/mcp/cards/
The server ships an MCP App: a card the client renders beside the chat. The card is where the human acts. Four tools exist only for it: `claim_business`, `open_session`, `apply_changeset`, `undo_changeset`.
### Visibility
These tools are registered with `visibility: ["app"]`, so a conforming client does not offer them to the model. Even if a model called one, it could not succeed: each requires a **card token**.
### Card tokens
When a tool returns a card, it also returns `_meta.cardTokens`: one token per click the card may make next, each bound to the signed-in user, one action, and one target.
| Returned by | Token | Lets the card call |
| --- | --- | --- |
| `find_business` | `open:<business_id>` for each editable match; `claim:<business_id>` for each claimable one | `open_session`, `claim_business` |
| `claim_business` | `open:<business_id>` | `open_session` |
| `propose_changes` | `apply` | `apply_changeset` |
| `apply_changeset` | `undo` | `undo_changeset` |
The server verifies the token against the caller, the action, and the target before forwarding to the Edits API. A token for applying changeset A cannot apply changeset B; a token minted for one user is refused for another.
### What the card shows
| View | After |
| --- | --- |
| `picker` | `find_business`: the matches, editable or locked, with Open or Claim. |
| `snapshot` | `get_snapshot`: what needs work, with a link to the console. |
| `changeset` | `propose_changes`, `apply_changeset`, `undo_changeset`: each item with its state and a tick box. |
| `session` | `open_session`: the business and the session's expiry. |
| `claimed` | `claim_business`: the claim's expiry. |
`structuredContent.view` names the view; the card reads the rest of `structuredContent` for its data.
### Without a card
A client with no MCP Apps support still gets the text results. `propose_changes` includes a console link where the user can apply the staged changeset in Keystone instead.
# Public API
Read a business's published data: profile, services, packages, team, FAQ, posts, forms.
The surface a business's website runs on. Every Keystone site, and any headless front end a business builds, reads its content from these endpoints with the site's API key. Responses are scoped to the one business the key belongs to. The `@keystone-sites/core` package wraps this surface.
- Audience: For businesses and partners
- Base URL: https://sor.keystone.app
- Authentication: Site API key, sent as the `X-API-Key` header.
- Endpoints: 42 in 22 groups
- HTML: https://developers.keystone.app/api/public/
## Public API: Ads config
1 endpoints. HTML: https://developers.keystone.app/api/public/ads-config/
### GET /api/v1/public/ads_config
Get ads config (Meta pixel id) for the calling site
Operation id: `get_ads_config_api_v1_public_ads_config_get`
Browser ad-tracking config for the generated site. Returns the business'
Meta pixel id (``{"meta_pixel_id": ...}``) so the site can init the Pixel.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Analytics config
1 endpoints. HTML: https://developers.keystone.app/api/public/analytics-config/
### GET /api/v1/public/analytics_config
Get per-site analytics config (PostHog, GTM, environment)
Operation id: `get_analytics_config_api_v1_public_analytics_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Blog posts
4 endpoints. HTML: https://developers.keystone.app/api/public/blog-posts/
### GET /api/v1/public/blog_posts
List published blog posts (public)
Operation id: `list_blog_posts_api_v1_public_blog_posts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict_str__Any___
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/blog_posts/by_slug/{slug}
Get a published blog post by slug (public)
Operation id: `get_blog_post_by_slug_api_v1_public_blog_posts_by_slug__slug__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/blog_posts/featured
List featured blog posts (public)
Operation id: `list_blog_posts_featured_api_v1_public_blog_posts_featured_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict_str__Any___
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/blog_posts/recent
List most recent published blog posts (public)
Operation id: `list_blog_posts_recent_api_v1_public_blog_posts_recent_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict_str__Any___
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Chat
3 endpoints. HTML: https://developers.keystone.app/api/public/chat/
### GET /api/v1/public/chat/config
Webchat config for the calling site (gates the widget)
Operation id: `get_chat_config_api_v1_public_chat_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/chat/messages
Fetch webchat history (long-poll for new messages)
Operation id: `get_chat_messages_api_v1_public_chat_messages_get`
Return messages with ``id > since`` (whole thread when ``since`` omitted).
Holds up to ``wait`` seconds (capped) until new messages arrive — the AI reply
lands async, so the held request returns as soon as it's persisted.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `session_id` | query | string | yes | |
| `since` | query | string \| null | no | |
| `wait` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/public/chat/messages
Send a webchat message (anonymous visitor)
Operation id: `send_chat_message_api_v1_public_chat_messages_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Request body** (application/json, required): ChatMessageBody
- `session_id` · string · required
- `body` · string · required
- `display_name` · string | null
- `email` · string | null
- `phone` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `fbp` · string | null
- `fbc` · string | null
- `page_url` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Company information
1 endpoints. HTML: https://developers.keystone.app/api/public/company-information/
### GET /api/v1/public/company_information
Get company information
Operation id: `get_company_information_api_v1_public_company_information_get`
Company information for the authenticated business. 404 if business not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicCompanyInformation_
- `request_id` · string · required
- `success` · true
- `data` · PublicCompanyInformation · required: Company information for GET /company_information. Matches legacy CompanyInformation.
- `id` · string · required
- `company_name` · string | null
- `tagline` · string | null
- `mission_statement_markdown` · string | null
- `about_text_markdown` · string | null
- `about_markdown` · string | null
- `description_markdown` · string | null
- `values_markdown` · string | null
- `values` · PublicCompanyValue[]
- `stats_markdown` · string | null
- `founded_year` · integer | null
- `business_hours` · PublicBusinessHours | object | null
- `external_management_url` · string | null
- `portal_url` · string | null
- `primary_phone` · string | null
- `primary_email` · string | null
- `address_line_1` · string | null
- `address_line_2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `support_email` · string | null
- `sales_email` · string | null
- `facebook_url` · string | null
- `instagram_url` · string | null
- `tiktok_url` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `youtube_url` · string | null
- `pinterest_url` · string | null
- `google_my_business_url` · string | null
- `yelp_url` · string | null
- `tripadvisor_url` · string | null
- `google_reviews_url` · string | null
- `logo_photo` · PublicPhoto | null
- `favicon_url` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `account_status` · string | null
- `chat_enabled` · boolean
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `managed_phone_numbers` · any[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: FAQ questions
2 endpoints. HTML: https://developers.keystone.app/api/public/faq-questions/
### GET /api/v1/public/faq_questions
List FAQ questions
Operation id: `list_faq_questions_api_v1_public_faq_questions_get`
All FAQ questions for the business, ordered by display_order. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicFaqQuestion__
- `request_id` · string · required
- `success` · true
- `data` · PublicFaqQuestion[] · required
- `id` · string · required
- `question` · string | null
- `answer` · string | null
- `answer_markdown` · string | null
- `featured` · boolean
- `category_slug` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/faq_questions/featured
List featured FAQ questions
Operation id: `list_faq_questions_featured_api_v1_public_faq_questions_featured_get`
FAQ questions where is_featured is True. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicFaqQuestion__
- `request_id` · string · required
- `success` · true
- `data` · PublicFaqQuestion[] · required
- `id` · string · required
- `question` · string | null
- `answer` · string | null
- `answer_markdown` · string | null
- `featured` · boolean
- `category_slug` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Form submissions
1 endpoints. HTML: https://developers.keystone.app/api/public/form-submissions/
### POST /api/v1/public/form_submissions
Submit a form
Operation id: `submit_form_api_v1_public_form_submissions_post`
Submit a form with field values.
Request body must include `formType` (not form id); the API resolves the form
definition for this account and stores the matching `form_id` server-side.
Auth via X-API-Key header.
Returns submission confirmation with optional event_id for ad tracking deduplication.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_PublicFormSubmissionResponse_
- `request_id` · string · required
- `success` · true
- `data` · PublicFormSubmissionResponse · required: Response for successful form submission.
- `submission_id` · string · required
- `form_type` · string · required
- `message` · string
- `event_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Forms
1 endpoints. HTML: https://developers.keystone.app/api/public/forms/
### GET /api/v1/public/forms/{form_type}
Get form by type
Operation id: `get_form_by_type_api_v1_public_forms__form_type__get`
Get a live form definition by identity (built-in or custom) for the business.
Returns the form's fields and settings needed to render it client-side. Only ``live``
forms are served; an unknown identity or a non-live form returns a typed 404
(``FORM_NOT_AVAILABLE``), never a 500. Auth via X-API-Key header.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `form_type` | path | string | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicForm_
- `request_id` · string · required
- `success` · true
- `data` · PublicForm · required: Form for public GET /forms/{form_type}. Matches legacy FormSerializer public view.
- `id` · string · required
- `name` · string · required
- `form_type` · string · required
- `fields` · (FormFieldSchema | FormFieldSchema[])[]
- `settings` · FormSettingsSchema: Settings for form behavior and consent flags.
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Industry media
1 endpoints. HTML: https://developers.keystone.app/api/public/industry-media/
### GET /api/v1/public/industry_media
Get curated industry fill-in imagery
Operation id: `get_industry_media_api_v1_public_industry_media_get`
Curated industry gallery photos for the business's industries (primary
first), best-first, same shape as /media_library with source="industry".
Fill-in imagery for creative/atmospheric use only — never for positions
presented as the business's own work (gallery, team, results).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicMediaLibrary_
- `request_id` · string · required
- `success` · true
- `data` · PublicMediaLibrary · required: GET /media_library and /industry_media — same shape for both photo pools (one photo shape for consumers): quality photos best-first. ``total`` is the pool-grade count, so ``total > len(photos)`` signals more good photos exist than the ``limit`` returned (default 60).
- `photos` · PublicMediaLibraryPhoto[]
- `total` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Job postings
3 endpoints. HTML: https://developers.keystone.app/api/public/job-postings/
### GET /api/v1/public/job_postings
List job postings (placeholder)
Operation id: `list_job_postings_api_v1_public_job_postings_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict_str__Any___
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/job_postings/by_slug/{slug}
Get job posting by slug (placeholder)
Operation id: `get_job_posting_by_slug_api_v1_public_job_postings_by_slug__slug__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/job_postings/featured
List featured job postings (placeholder)
Operation id: `list_job_postings_featured_api_v1_public_job_postings_featured_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict_str__Any___
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: LLMs
1 endpoints. HTML: https://developers.keystone.app/api/public/llms/
### GET /api/v1/public/llms/current
Serve the website llms.txt (Markdown)
Operation id: `get_llms_current_api_v1_public_llms_current_get`
Serve the latest persisted llms.txt.
Cold start (no version yet): build + persist once, then serve. A version cut before the
column existed has ``llms_txt`` NULL: serve a fresh render for it (no persistence) and
schedule the debounced refresh that will store it.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Locations
4 endpoints. HTML: https://developers.keystone.app/api/public/locations/
### GET /api/v1/public/locations
List locations
Operation id: `list_locations_api_v1_public_locations_get`
All active locations for the business, ordered by display_order. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicLocation__
- `request_id` · string · required
- `success` · true
- `data` · PublicLocation[] · required
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `address_line_1` · string | null
- `address_line_2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `is_primary` · boolean
- `active` · boolean
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `description_markdown` · string | null
- `timezone` · string | null
- `business_hours` · PublicBusinessHours | object | null
- `photo_attachments` · PublicPhotoAttachment[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/locations/{location_id}
Get location by ID
Operation id: `get_location_by_id_api_v1_public_locations__location_id__get`
Get a single location by id. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `location_id` | path | string (uuid) | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicLocation_
- `request_id` · string · required
- `success` · true
- `data` · PublicLocation · required: Location for GET /locations and by id/slug. Matches legacy Location.
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `address_line_1` · string | null
- `address_line_2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `is_primary` · boolean
- `active` · boolean
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `description_markdown` · string | null
- `timezone` · string | null
- `business_hours` · PublicBusinessHours | object | null
- `photo_attachments` · PublicPhotoAttachment[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/locations/by_slug/{slug}
Get location by slug
Operation id: `get_location_by_slug_api_v1_public_locations_by_slug__slug__get`
Get a single location by slug. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicLocation_
- `request_id` · string · required
- `success` · true
- `data` · PublicLocation · required: Location for GET /locations and by id/slug. Matches legacy Location.
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `address_line_1` · string | null
- `address_line_2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `is_primary` · boolean
- `active` · boolean
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `description_markdown` · string | null
- `timezone` · string | null
- `business_hours` · PublicBusinessHours | object | null
- `photo_attachments` · PublicPhotoAttachment[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/locations/primary
Get primary location (real or placeholder)
Operation id: `get_primary_location_api_v1_public_locations_primary_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Media library
1 endpoints. HTML: https://developers.keystone.app/api/public/media-library/
### GET /api/v1/public/media_library
Get the business media library (good photos, best-first)
Operation id: `get_media_library_api_v1_public_media_library_get`
The business's best good photos with CDN URLs, dimensions, alt, hero-grade
flag, and the website_photos slot each already fills (if any). Quality-filtered,
best-first, capped at ``limit`` (default 60) — a creative palette for the whole
site; consumers curate further per context (e.g. the gallery renders fewer).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicMediaLibrary_
- `request_id` · string · required
- `success` · true
- `data` · PublicMediaLibrary · required: GET /media_library and /industry_media — same shape for both photo pools (one photo shape for consumers): quality photos best-first. ``total`` is the pool-grade count, so ``total > len(photos)`` signals more good photos exist than the ``limit`` returned (default 60).
- `photos` · PublicMediaLibraryPhoto[]
- `total` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Messages
2 endpoints. HTML: https://developers.keystone.app/api/public/messages/
### GET /api/v1/public/messages
List messages (placeholder)
Operation id: `list_messages_api_v1_public_messages_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict_str__Any___
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/public/messages
Send message (placeholder)
Operation id: `send_message_api_v1_public_messages_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Packages
3 endpoints. HTML: https://developers.keystone.app/api/public/packages/
### GET /api/v1/public/packages
List packages
Operation id: `list_packages_api_v1_public_packages_get`
All packages for the business, ordered by display_order. Paginated. Member services not included.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicPackage__
- `request_id` · string · required
- `success` · true
- `data` · PublicPackage[] · required
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `package_items` · PublicPackageItem[]
- `offers` · PublicOffer[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/packages/by_slug/{slug}
Get package by slug
Operation id: `get_package_by_slug_api_v1_public_packages_by_slug__slug__get`
Get a single package by slug, with member services nested. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicPackage_
- `request_id` · string · required
- `success` · true
- `data` · PublicPackage · required: Package for GET /packages and by_slug. `cost` serializes as a decimal string (e.g. "99.99").
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `package_items` · PublicPackageItem[]
- `offers` · PublicOffer[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/packages/featured
List featured packages
Operation id: `list_packages_featured_api_v1_public_packages_featured_get`
Packages where is_featured is True. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicPackage__
- `request_id` · string · required
- `success` · true
- `data` · PublicPackage[] · required
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `package_items` · PublicPackageItem[]
- `offers` · PublicOffer[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Reviews
2 endpoints. HTML: https://developers.keystone.app/api/public/reviews/
### GET /api/v1/public/reviews
List reviews
Operation id: `list_reviews_api_v1_public_reviews_get`
List public reviews for the business (legacy-compatible, non-paginated).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicReview__
- `request_id` · string · required
- `success` · true
- `data` · PublicReview[] · required
- `id` · string · required
- `reviewer_name` · string · required
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer · required
- `content_markdown` · string | null
- `source` · string | null
- `source_url` · string | null
- `featured` · boolean
- `reviewed_at` · string | null
- `created_at` · string | null
- `updated_at` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/reviews/featured
List featured reviews
Operation id: `list_featured_reviews_api_v1_public_reviews_featured_get`
List featured public reviews for the business (legacy-compatible, non-paginated).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicReview__
- `request_id` · string · required
- `success` · true
- `data` · PublicReview[] · required
- `id` · string · required
- `reviewer_name` · string · required
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer · required
- `content_markdown` · string | null
- `source` · string | null
- `source_url` · string | null
- `featured` · boolean
- `reviewed_at` · string | null
- `created_at` · string | null
- `updated_at` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Service items
3 endpoints. HTML: https://developers.keystone.app/api/public/service-items/
### GET /api/v1/public/service-items
List service items
Operation id: `list_service_items_api_v1_public_service_items_get`
All service items for the business, ordered by display_order. Paginated. Member services not included.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicServiceItem__
- `request_id` · string · required
- `success` · true
- `data` · PublicServiceItem[] · required
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `services` · PublicService[]
- `offers` · PublicOffer[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/service-items/by_slug/{slug}
Get service item by slug
Operation id: `get_service_item_by_slug_api_v1_public_service_items_by_slug__slug__get`
Get a single service item by slug, with member services nested. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicServiceItem_
- `request_id` · string · required
- `success` · true
- `data` · PublicServiceItem · required: Service item for GET /service-items and by_slug. `cost` serializes as a decimal string (e.g. "19.99").
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `services` · PublicService[]
- `offers` · PublicOffer[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/service-items/featured
List featured service items
Operation id: `list_service_items_featured_api_v1_public_service_items_featured_get`
Service items where is_featured is True. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicServiceItem__
- `request_id` · string · required
- `success` · true
- `data` · PublicServiceItem[] · required
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `services` · PublicService[]
- `offers` · PublicOffer[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Services
3 endpoints. HTML: https://developers.keystone.app/api/public/services/
### GET /api/v1/public/services
List services
Operation id: `list_services_api_v1_public_services_get`
All services for the business, ordered by display_order. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicService__
- `request_id` · string · required
- `success` · true
- `data` · PublicService[] · required
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `service_items` · PublicServiceItem[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/services/by_slug/{slug}
Get service by slug
Operation id: `get_service_by_slug_api_v1_public_services_by_slug__slug__get`
Get a single service by slug. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicService_
- `request_id` · string · required
- `success` · true
- `data` · PublicService · required: Service for GET /services and by_slug. Matches legacy Service.
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `service_items` · PublicServiceItem[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/services/featured
List featured services
Operation id: `list_services_featured_api_v1_public_services_featured_get`
Services where is_featured is True. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicService__
- `request_id` · string · required
- `success` · true
- `data` · PublicService[] · required
- `id` · string · required
- `name` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `service_items` · PublicServiceItem[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Sitemap
1 endpoints. HTML: https://developers.keystone.app/api/public/sitemap/
### GET /api/v1/public/sitemap/current
Serve the website sitemap (XML)
Operation id: `get_sitemap_current_api_v1_public_sitemap_current_get`
Serve the latest persisted sitemap.
If nothing is stored yet (cold start), build + persist once and serve. The cold build
skips per-file commit lookups (lastmod = now) so the first request never waits on them;
a debounced background refresh then fills in the real dates. If that persist can't
complete, fall back to building the sitemap on the fly so a real site always gets a
valid response.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: string
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Social posts
1 endpoints. HTML: https://developers.keystone.app/api/public/social-posts/
### GET /api/v1/public/social_posts
List published social posts (public)
Operation id: `list_social_posts_api_v1_public_social_posts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicSocialPost__
- `request_id` · string · required
- `success` · true
- `data` · PublicSocialPost[] · required
- `id` · string · required
- `platform` · string · required
- `content_markdown` · string | null
- `posted_at` · string | null
- `status` · string | null
- `created_at` · string | null
- `updated_at` · string | null
- `image_urls` · string[]
- `video_urls` · string[]
- `photo_attachments` · PublicPhotoAttachment[]
- `engagement_metrics` · object | null
- `external_post_url` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Team members
2 endpoints. HTML: https://developers.keystone.app/api/public/team-members/
### GET /api/v1/public/team_members
List team members
Operation id: `list_team_members_api_v1_public_team_members_get`
All team members for the business, ordered by display_order. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicTeamMember__
- `request_id` · string · required
- `success` · true
- `data` · PublicTeamMember[] · required
- `id` · string · required
- `name` · string | null
- `role` · string | null
- `position` · string | null
- `bio_markdown` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/public/team_members/featured
List featured team members
Operation id: `list_team_members_featured_api_v1_public_team_members_featured_get`
Team members where is_featured is True. Paginated.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | |
| `per_page` | query | integer | no | |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PublicTeamMember__
- `request_id` · string · required
- `success` · true
- `data` · PublicTeamMember[] · required
- `id` · string · required
- `name` · string | null
- `role` · string | null
- `position` · string | null
- `bio_markdown` · string | null
- `photo_attachments` · PublicPhotoAttachment[]
- `featured` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Public API: Website photos
1 endpoints. HTML: https://developers.keystone.app/api/public/website-photos/
### GET /api/v1/public/website_photos
Get website photos
Operation id: `get_website_photos_api_v1_public_website_photos_get`
Website photos (logo, favicon, hero, etc.) for the business's website with CDN URLs.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PublicWebsitePhotos_
- `request_id` · string · required
- `success` · true
- `data` · PublicWebsitePhotos · required: Website photos for GET /website_photos. Config-driven: arbitrary key (website_photos_key) → PublicWebsitePhoto \| null; default config yields same keys as legacy (logo, favicon, hero, etc.).
- `stock_photos` · PublicWebsitePhoto[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
# Edits API
Sessions, snapshots, staged changesets, apply and undo: the contract behind the MCP server.
A small, stable contract for making reviewed changes to a business's information. A caller opens a session on a business it may edit, reads a snapshot, stages a changeset, and applies or reverts it. Every applied change lands in the console's Change History with Undo. The Keystone MCP server calls this surface and nothing else.
- Audience: For businesses and partners
- Base URL: https://sor.keystone.app
- Authentication: Console token, or MCP token with service key. `Authorization: Bearer <token>`. A console user token on its own, or an MCP access token issued by Heimdal together with `X-Internal-Api-Key` holding the MCP server's service key.
- Endpoints: 14 in 6 groups
- HTML: https://developers.keystone.app/api/edits/
## Edits API: Business claims
1 endpoints. HTML: https://developers.keystone.app/api/edits/businesses-claim/
### PUT /api/v1/edits/businesses/{business_id}/claim
Claim a business nobody holds, for 24 hours
Operation id: `claim_business_api_v1_edits_businesses__business_id__claim_put`
The console's claim, for the business picker in Claude too: keystone-mcp passes a claim on only
with the card token of the growth partner's click on that card, never on the model's say-so.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ClaimStateData_
- `request_id` · string · required
- `success` · true
- `data` · ClaimStateData · required: Everything the console's claim control shows.
- `business_id` · string · required
- `claim` · ClaimData | null
- `launched` · boolean · required
- `can_claim` · boolean · required
- `can_extend` · boolean · required
- `server_time` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Edits API: Businesses
1 endpoints. HTML: https://developers.keystone.app/api/edits/businesses/
### GET /api/v1/edits/businesses
Find businesses by name, site or domain, marked with whether the caller can edit them
Operation id: `find_businesses_api_v1_edits_businesses_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `q` | query | string | yes | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_BusinessMatchData__
- `request_id` · string · required
- `success` · true
- `data` · BusinessMatchData[] · required
- `business_id` · string · required
- `company_name` · string · required
- `website_url` · string | null
- `city` · string | null
- `state` · string | null
- `custom_domain` · string | null
- `preview_domain` · string | null
- `launched` · boolean · required
- `claim` · ClaimData | null
- `editable` · boolean · required
- `locked_reason` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Edits API: Businesses change log
1 endpoints. HTML: https://developers.keystone.app/api/edits/businesses-change-log/
### GET /api/v1/edits/businesses/{business_id}/change-log
Every change to the business's records, by any writer, newest first
Operation id: `list_change_log_api_v1_edits_businesses__business_id__change_log_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `before_id` | query | integer \| null | no | |
| `changeset_id` | query | string (uuid) \| null | no | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ChangeLogEntryData__
- `request_id` · string · required
- `success` · true
- `data` · ChangeLogEntryData[] · required
- `id` · integer · required
- `created_at` · string (date-time) · required
- `entity` · string · required
- `record_id` · string · required
- `op` · string · required
- `field_path` · string | null
- `label` · string | null
- `before_value` · any | null
- `after_value` · any | null
- `actor_type` · string · required
- `actor_id` · string | null
- `actor_name` · string | null
- `client_id` · string | null
- `surface` · string · required
- `changeset_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Edits API: Businesses changesets
1 endpoints. HTML: https://developers.keystone.app/api/edits/businesses-changesets/
### GET /api/v1/edits/businesses/{business_id}/changesets
The business's changesets, newest first
Operation id: `list_changesets_api_v1_edits_businesses__business_id__changesets_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `before` | query | string (date-time) \| null | no | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ChangesetSummaryData__
- `request_id` · string · required
- `success` · true
- `data` · ChangesetSummaryData[] · required
- `id` · string · required
- `status` · string · required
- `summary` · string | null
- `author_id` · string · required
- `author_name` · string | null
- `client_id` · string · required
- `created_at` · string (date-time) · required
- `applied_at` · string (date-time) | null
- `reverted_at` · string (date-time) | null
- `items` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Edits API: Changesets
5 endpoints. HTML: https://developers.keystone.app/api/edits/changesets/
### GET /api/v1/edits/changesets/{changeset_id}
A changeset and its items
Operation id: `get_changeset_api_v1_edits_changesets__changeset_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `changeset_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ChangesetData_
- `request_id` · string · required
- `success` · true
- `data` · ChangesetData · required
- `id` · string · required
- `session_id` · string · required
- `business_id` · string · required
- `company_name` · string · required
- `client_id` · string · required
- `status` · string · required
- `summary` · string | null
- `created_at` · string (date-time) · required
- `applied_at` · string (date-time) | null
- `reverted_at` · string (date-time) | null
- `items` · ChangesetItemData[] · required
- `replayed` · boolean
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/edits/changesets/{changeset_id}/apply
Apply a staged changeset's ticked items, each only if its value has not moved
Operation id: `apply_changeset_api_v1_edits_changesets__changeset_id__apply_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `changeset_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, optional): ApplyChangesetBody | null
- `item_ids` · string (uuid)[] | null
**Responses**
- `200` Successful Response: SuccessResponse_ChangesetData_
- `request_id` · string · required
- `success` · true
- `data` · ChangesetData · required
- `id` · string · required
- `session_id` · string · required
- `business_id` · string · required
- `company_name` · string · required
- `client_id` · string · required
- `status` · string · required
- `summary` · string | null
- `created_at` · string (date-time) · required
- `applied_at` · string (date-time) | null
- `reverted_at` · string (date-time) | null
- `items` · ChangesetItemData[] · required
- `replayed` · boolean
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/edits/changesets/{changeset_id}/discard
Drop a staged changeset
Operation id: `discard_changeset_api_v1_edits_changesets__changeset_id__discard_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `changeset_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ChangesetData_
- `request_id` · string · required
- `success` · true
- `data` · ChangesetData · required
- `id` · string · required
- `session_id` · string · required
- `business_id` · string · required
- `company_name` · string · required
- `client_id` · string · required
- `status` · string · required
- `summary` · string | null
- `created_at` · string (date-time) · required
- `applied_at` · string (date-time) | null
- `reverted_at` · string (date-time) | null
- `items` · ChangesetItemData[] · required
- `replayed` · boolean
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/edits/changesets/{changeset_id}/items/{item_id}
Tick or untick one staged item
Operation id: `select_item_api_v1_edits_changesets__changeset_id__items__item_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `changeset_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): ItemSelectionBody
- `selected` · boolean · required
**Responses**
- `200` Successful Response: SuccessResponse_ChangesetItemData_
- `request_id` · string · required
- `success` · true
- `data` · ChangesetItemData · required
- `id` · string · required
- `position` · integer · required
- `entity` · string · required
- `entity_name` · string · required
- `op` · string · required
- `record_id` · string | null
- `record_label` · string | null
- `field_path` · string | null
- `label` · string | null
- `before_value` · any | null
- `after_value` · any | null
- `flags` · object[]
- `selected` · boolean · required
- `status` · string · required
- `error` · object | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/edits/changesets/{changeset_id}/revert
Undo an applied changeset where nothing changed since
Operation id: `revert_changeset_api_v1_edits_changesets__changeset_id__revert_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `changeset_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ChangesetData_
- `request_id` · string · required
- `success` · true
- `data` · ChangesetData · required
- `id` · string · required
- `session_id` · string · required
- `business_id` · string · required
- `company_name` · string · required
- `client_id` · string · required
- `status` · string · required
- `summary` · string | null
- `created_at` · string (date-time) · required
- `applied_at` · string (date-time) | null
- `reverted_at` · string (date-time) | null
- `items` · ChangesetItemData[] · required
- `replayed` · boolean
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Edits API: Sessions
5 endpoints. HTML: https://developers.keystone.app/api/edits/sessions/
### POST /api/v1/edits/sessions
Open an edit session for one business the caller holds
Operation id: `open_session_api_v1_edits_sessions_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): OpenSessionBody
- `business_id` · string (uuid) · required
**Responses**
- `201` Successful Response: SuccessResponse_EditSessionData_
- `request_id` · string · required
- `success` · true
- `data` · EditSessionData · required
- `id` · string · required
- `business_id` · string · required
- `company_name` · string · required
- `client_id` · string · required
- `status` · string · required
- `ended_reason` · string | null
- `created_at` · string (date-time) · required
- `expires_at` · string (date-time) · required
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/edits/sessions/{session_id}
An edit session, and whether it is still open
Operation id: `get_session_api_v1_edits_sessions__session_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `session_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_EditSessionData_
- `request_id` · string · required
- `success` · true
- `data` · EditSessionData · required
- `id` · string · required
- `business_id` · string · required
- `company_name` · string · required
- `client_id` · string · required
- `status` · string · required
- `ended_reason` · string | null
- `created_at` · string (date-time) · required
- `expires_at` · string (date-time) · required
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/edits/sessions/{session_id}/changesets
Check proposed changes and stage them as one changeset
Operation id: `stage_changeset_api_v1_edits_sessions__session_id__changesets_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `session_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): StageChangesetBody
- `summary` · string | null
- `idempotency_key` · string | null
- `items` · ChangeItemIn[] · required
- `entity` · string · required
- `op` · string · required
- `record_id` · string | null
- `fields` · object
- `expected` · object | null
- `version` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_ChangesetData_
- `request_id` · string · required
- `success` · true
- `data` · ChangesetData · required
- `id` · string · required
- `session_id` · string · required
- `business_id` · string · required
- `company_name` · string · required
- `client_id` · string · required
- `status` · string · required
- `summary` · string | null
- `created_at` · string (date-time) · required
- `applied_at` · string (date-time) | null
- `reverted_at` · string (date-time) | null
- `items` · ChangesetItemData[] · required
- `replayed` · boolean
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/edits/sessions/{session_id}/records/{entity}/{record_id}
One record in full, with its version (the profile is record_id 'profile')
Operation id: `read_record_api_v1_edits_sessions__session_id__records__entity___record_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `session_id` | path | string (uuid) | yes | |
| `entity` | path | string | yes | |
| `record_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_RecordData_
- `request_id` · string · required
- `success` · true
- `data` · RecordData · required
- `entity` · string · required
- `record_id` · string · required
- `label` · string · required
- `version` · string · required
- `record` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/edits/sessions/{session_id}/snapshot
Everything the session's business has: the profile in full, other records trimmed
Operation id: `get_snapshot_api_v1_edits_sessions__session_id__snapshot_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `session_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SnapshotData_
- `request_id` · string · required
- `success` · true
- `data` · SnapshotData · required
- `business_id` · string · required
- `company_name` · string · required
- `profile` · object · required
- `profile_version` · string · required
- `entities` · EntitySnapshotData[] · required
- `business` · BusinessIdentityData · required: What tells a business apart from others with its name: where it is online and in town, and whether its site is live. Every session, snapshot and changeset carries it, so a card can show which business a change is for.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
# Console API
Everything the Keystone console does: businesses, contacts, website, ads, social, billing, agents.
The surface `go.keystone.app` is built on. Operations are scoped by the caller's console account and its business memberships; staff accounts see every business. This is the largest surface, grouped below by the resource each operation manages. Paths under `/admin` are the console's own; paths under `/businesses` and `/ai` are per-business agent and AI-provider controls.
- Audience: Console and first-party apps
- Base URL: https://sor.keystone.app
- Authentication: Console user token. `Authorization: Bearer <token>`, a Heimdal access token for a console user.
- Endpoints: 434 in 59 groups
- HTML: https://developers.keystone.app/api/console/
## Console API: Accounts
11 endpoints. HTML: https://developers.keystone.app/api/console/accounts/
### POST /api/v1/admin/account-user-invites/{invite_id}/accept
Accept a pending account invite for the authenticated user
Operation id: `accept_account_invite_api_v1_admin_account_user_invites__invite_id__accept_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `invite_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AccountInviteConsumeData_
- `request_id` · string · required
- `success` · true
- `data` · AccountInviteConsumeData · required
- `business_id` · string · required
- `user` · AccountUserData · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/accounts
List admin account summaries
Operation id: `list_accounts_api_v1_admin_accounts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AccountSummaryData__
- `request_id` · string · required
- `success` · true
- `data` · AccountSummaryData[] · required
- `id` · string · required
- `business_name` · string · required
- `primary_industry` · string · required
- `secondary_industries` · string[]
- `user_count` · integer · required
- `status` · string · required
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/accounts
Create account
Operation id: `create_account_api_v1_admin_accounts_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AccountCreateBody
- `business_name` · string · required
- `primary_industry_id` · string · required
- `secondary_industry_ids` · string[]
- `primary_user` · AccountPrimaryUserBody | null
- `first_name` · string · required
- `last_name` · string · required
- `email` · string (email) · required
**Responses**
- `201` Successful Response: SuccessResponse_AccountCreateData_
- `request_id` · string · required
- `success` · true
- `data` · AccountCreateData · required
- `account` · AccountSummaryData · required
- `primary_user_id` · string (uuid) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/accounts/{business_id}/users
List account users and pending invites
Operation id: `list_account_users_api_v1_admin_accounts__business_id__users_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AccountUserData__
- `request_id` · string · required
- `success` · true
- `data` · AccountUserData[] · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/accounts/{business_id}/users
Invite an account user (pending invite + email)
Operation id: `invite_account_user_api_v1_admin_accounts__business_id__users_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AccountUserInviteBody
- `email` · string (email) · required
- `phone` · string | null
- `role` · string
**Responses**
- `201` Successful Response: SuccessResponse_AccountUserData_
- `request_id` · string · required
- `success` · true
- `data` · AccountUserData · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/accounts/{business_id}/users/{entry_id}
Update an account user or pending invite
Operation id: `update_account_user_api_v1_admin_accounts__business_id__users__entry_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `entry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AccountUserUpdateBody
- `first_name` · string · required
- `last_name` · string · required
- `email` · string (email) · required
- `phone` · string | null
- `role` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_AccountUserData_
- `request_id` · string · required
- `success` · true
- `data` · AccountUserData · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/accounts/{business_id}/users/{entry_id}
Delete an account user link or pending invite
Operation id: `delete_account_user_api_v1_admin_accounts__business_id__users__entry_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `entry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/accounts/{business_id}/users/{entry_id}/resend
Re-send the invitation email for a pending invite
Operation id: `resend_account_invite_api_v1_admin_accounts__business_id__users__entry_id__resend_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `entry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AccountUserData_
- `request_id` · string · required
- `success` · true
- `data` · AccountUserData · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/accounts/from-website
Create account from a website URL (streamlined onboarding)
Operation id: `create_account_from_website_api_v1_admin_accounts_from_website_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AccountCreateFromWebsiteBody
- `website_url` · string · required
- `primary_industry_id` · string | null
- `secondary_industry_ids` · string[]
- `primary_user` · AccountPrimaryUserBody | null
- `first_name` · string · required
- `last_name` · string · required
- `email` · string (email) · required
- `user_prompt` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_AccountCreateFromWebsiteData_
- `request_id` · string · required
- `success` · true
- `data` · AccountCreateFromWebsiteData · required
- `account` · AccountSummaryData · required
- `primary_user_id` · string (uuid) | null
- `scrape_run_id` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/me/businesses
List businesses linked to the authenticated user
Operation id: `list_my_businesses_api_v1_admin_me_businesses_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_MyBusinessSummary__
- `request_id` · string · required
- `success` · true
- `data` · MyBusinessSummary[] · required
- `id` · string · required
- `business_name` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/me/businesses
Create the authenticated user's own business (self-serve onboarding)
Operation id: `create_my_business_api_v1_admin_me_businesses_post`
Beside GET /me/businesses on purpose: `/api/v1/admin` is the console BFF's prefix, not an
admin-only surface, and the router's subscription gate no-ops without a `business_id` in the path —
a user with no subscribed business must be able to create one. Always 200; `created` says whether
this call made the business or returned the one the caller already had (see the service).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): MyBusinessCreateBody
- `business_name` · string · required
- `website_url` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_MyBusinessCreateData_
- `request_id` · string · required
- `success` · true
- `data` · MyBusinessCreateData · required: ``created`` is false when the caller already had an active business and got that one back — what makes a retry of the create call safe.
- `business` · MyBusinessSummary · required
- `created` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads accounts
4 endpoints. HTML: https://developers.keystone.app/api/console/ads-accounts/
### GET /api/v1/admin/businesses/{business_id}/ads/accounts
List ad accounts for the business
Operation id: `list_ad_accounts_api_v1_admin_businesses__business_id__ads_accounts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `external_platform` | query | string \| null | no | |
| `include_revoked` | query | boolean | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AdAccountData__
- `request_id` · string · required
- `success` · true
- `data` · AdAccountData[] · required
- `id` · string (uuid) · required
- `businessId` · string (uuid) · required
- `externalPlatform` · string · required
- `externalPlatformAccountId` · string · required
- `externalPlatformBusinessId` · string | null
- `defaultPageId` · string | null
- `currencyCode` · string · required
- `timezoneName` · string · required
- `connectedAt` · string (date-time) · required
- `lastValidatedAt` · string (date-time) | null
- `revokedAt` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/ads/accounts/{ads_account_id}/pages
List Pages on a connected ad account (live)
Operation id: `list_pages_api_v1_admin_businesses__business_id__ads_accounts__ads_account_id__pages_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ads_account_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AdPageData__
- `request_id` · string · required
- `success` · true
- `data` · AdPageData[] · required
- `id` · string · required
- `name` · string · required
- `category` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/accounts/{ads_account_id}/revoke
Revoke an ad-account link (soft).
Operation id: `revoke_account_api_v1_admin_businesses__business_id__ads_accounts__ads_account_id__revoke_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ads_account_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/discovery/import
Discover ad accounts + pages from provider; upsert ads_accounts
Operation id: `discovery_import_api_v1_admin_businesses__business_id__ads_discovery_import_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): DiscoveryImportBody
- `externalPlatform` · string · required
- `oauthConnectionId` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_DiscoveryImportData_
- `request_id` · string · required
- `success` · true
- `data` · DiscoveryImportData · required
- `accounts` · AdAccountData[] · required
- `pages` · AdPageData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads agent
2 endpoints. HTML: https://developers.keystone.app/api/console/ads-agent/
### GET /api/v1/businesses/{business_id}/ads-agent/config
Get effective ads-agent settings for a business
Operation id: `get_config_api_v1_businesses__business_id__ads_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SimpleAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SimpleAgentConfigData · required: Shared shape for contacts / ads / listings agents.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/ads-agent/config
Update ads-agent settings for a business
Operation id: `patch_config_api_v1_businesses__business_id__ads_agent_config_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SimpleAgentConfigPatchBody
- `enabled` · boolean | null
- `instructions` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SimpleAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SimpleAgentConfigData · required: Shared shape for contacts / ads / listings agents.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads campaigns
13 endpoints. HTML: https://developers.keystone.app/api/console/ads-campaigns/
### GET /api/v1/admin/businesses/{business_id}/ads/campaigns
List campaigns
Operation id: `list_campaigns_api_v1_admin_businesses__business_id__ads_campaigns_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `platform` | query | string \| null | no | |
| `q` | query | string \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_CampaignData__
- `request_id` · string · required
- `success` · true
- `data` · CampaignData[] · required
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns
Create a draft campaign
Operation id: `create_campaign_api_v1_admin_businesses__business_id__ads_campaigns_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CampaignCreateBody
- `name` · string · required
- `platform` · "meta"
- `objective` · "OUTCOME_LEADS" | "OUTCOME_TRAFFIC" | "OUTCOME_SALES"
- `adsAccountId` · string (uuid) | null
- `leadFollowUpEnabled` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}
Get a campaign with embedded ad-sets and units
Operation id: `get_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `refreshFromMeta` | query | boolean | no | When true (default), pull this campaign's latest state from Meta and reconcile SOR before returning — the SAME full sync as the per-campaign Sync button, but due-gated (``ads_campaign_sync_min_interval_seconds``) so opening the page repeatedly doesn't hammer Meta. Set ``refreshFromMeta=false`` to read SOR only. |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}
Patch campaign name (status changes use dedicated verbs)
Operation id: `patch_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CampaignPatchBody
- `name` · string | null
- `leadFollowUpEnabled` · boolean | null
- `isManaged` · boolean | null
- `objective` · "OUTCOME_LEADS" | "OUTCOME_TRAFFIC" | "OUTCOME_SALES" | null
- `budgetLevel` · "campaign" | "ad_set" | null
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · "LOWEST_COST_WITHOUT_CAP" | "COST_CAP" | "LOWEST_COST_WITH_BID_CAP" | null
- `bidAmountMinor` · integer | null
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/archive
Archive locally + cascade soft-delete children
Operation id: `archive_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__archive_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/duplicate
Deep-copy campaign + sets + units as a new draft
Operation id: `duplicate_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__duplicate_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/launch
Validate + push to provider + set Active
Operation id: `launch_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__launch_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CampaignLaunchBody
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/pause
Pause on provider + locally
Operation id: `pause_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__pause_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/resume
Resume on provider + locally
Operation id: `resume_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__resume_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/sync
Pull this one campaign's latest state from Meta and return it
Operation id: `sync_campaign_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__sync_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CampaignData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignData · required: Mirror of `Campaign` interface in `keystone-console/src/pages/ads/ads-fixtures.ts`, with v2 field renames (D27): ``cpl`` → ``costPerLead``, ``cplTrendPercent`` → ``costPerLeadTrendPercent``, ``ctaType`` → ``callToActionType``.
- `id` · string (uuid) · required
- `name` · string · required
- `status` · "draft" | "active" | "paused" | "archived" · required
- `platform` · "meta" | "google" · required
- `adSetCount` · integer · required
- `adUnitCount` · integer · required
- `budgetLabel` · string · required
- `adSets` · AdSetData[]
- `costPerLead` · number | null
- `costPerLeadTrendPercent` · integer | null
- `reach` · integer | null
- `leads` · integer | null
- `targetingCity` · string | null
- `targetingRadiusMiles` · integer | null
- `budgetDailyUsd` · integer | null
- `isClientCreated` · boolean | null
- `currencyCode` · string
- `leadFollowUpEnabled` · boolean
- `createdVia` · "manual" | "agent" | "external"
- `isExternallyManaged` · boolean
- `isManaged` · boolean
- `startedAt` · string | null
- `objective` · string | null
- `budgetLevel` · string
- `dailyBudgetMinor` · integer | null
- `bidStrategy` · string
- `bidAmountMinor` · integer | null
- `externalPlatformCampaignId` · string | null
- `predecessorCampaignId` · string (uuid) | null
- `performanceFlag` · CampaignListFlagSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/agent
Trigger the campaign-creation agent (workflow engine)
Operation id: `trigger_campaign_agent_api_v1_admin_businesses__business_id__ads_campaigns_agent_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CampaignAgentTriggerBody
- `prompt` · string · required
- `adsAccountId` · string (uuid) | null
- `campaignId` · string (uuid) | null
- `adSetId` · string (uuid) | null
- `unitId` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_CampaignAgentTriggerData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignAgentTriggerData · required: Response — FE polls thread/run status from here.
- `workflowRunId` · string · required
- `threadId` · string · required
- `status` · string · required
- `assistantReply` · string
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/ads/campaigns/agent/messages
List campaign-agent chat transcript
Operation id: `list_campaign_agent_messages_api_v1_admin_businesses__business_id__ads_campaigns_agent_messages_get`
Return the persisted chat transcript for a thread.
The POST relay (``/agent/messages``) only returns the latest
assistant reply per turn, so user-typed messages disappear from
the FE on refresh. This GET reads the workflow-engine's
``thread_events`` (via control-plane ``/threads/{id}/detail``)
and reduces them to the chat-message subset so the FE can render
the full transcript and survive page reloads.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `threadId` | query | string | yes | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ChatTranscriptData_
- `request_id` · string · required
- `success` · true
- `data` · ChatTranscriptData · required: Response wrapper for ``GET /campaigns/agent/messages``.
- `threadId` · string · required
- `messages` · ChatMessageData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/agent/messages
Send a follow-up turn on an existing agent thread
Operation id: `send_campaign_agent_message_api_v1_admin_businesses__business_id__ads_campaigns_agent_messages_post`
Multi-turn relay so the FE doesn't need to talk to the router
directly. The initial ``POST /campaigns/agent`` may return an empty
``workflowRunId``; keep POSTing here with the same ``threadId``
until a workflow run is started and a proposal widget appears.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CampaignAgentMessageBody
- `threadId` · string · required
- `message` · string · required
- `campaignId` · string (uuid) | null
- `adSetId` · string (uuid) | null
- `unitId` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_CampaignAgentTriggerData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignAgentTriggerData · required: Response — FE polls thread/run status from here.
- `workflowRunId` · string · required
- `threadId` · string · required
- `status` · string · required
- `assistantReply` · string
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads performance
1 endpoints. HTML: https://developers.keystone.app/api/console/ads-performance/
### GET /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/performance
Campaign daily performance series + ladder flags
Operation id: `get_campaign_performance_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__performance_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `windowDays` | query | integer | no | |
| `from` | query | string (date) \| null | no | |
| `to` | query | string (date) \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CampaignPerformanceData_
- `request_id` · string · required
- `success` · true
- `data` · CampaignPerformanceData · required
- `series` · PerformanceSeriesPoint[] · required
- `firstDataDate` · string | null
- `lastDataDate` · string | null
- `lastSyncAt` · string | null
- `tokenFailing` · boolean
- `campaignFlag` · FlagSummary | null
- `adSetFlags` · AdSetFlagSummary[]
- `adUnitFlags` · AdUnitFlagSummary[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads refresh recommendations
4 endpoints. HTML: https://developers.keystone.app/api/console/ads-refresh-recommendations/
### GET /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/refresh-recommendations
List Refresh Recommendations
Operation id: `list_refresh_recommendations_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__refresh_recommendations_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict__
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/refresh-recommendations/{recommendation_id}/accept
Accept Refresh Recommendation
Operation id: `accept_refresh_recommendation_api_v1_admin_businesses__business_id__ads_refresh_recommendations__recommendation_id__accept_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `recommendation_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/refresh-recommendations/{recommendation_id}/dismiss
Dismiss Refresh Recommendation
Operation id: `dismiss_refresh_recommendation_api_v1_admin_businesses__business_id__ads_refresh_recommendations__recommendation_id__dismiss_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `recommendation_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/refresh-recommendations/{recommendation_id}/replace-draft-media
Replace Refresh Draft Media
Operation id: `replace_refresh_draft_media_api_v1_admin_businesses__business_id__ads_refresh_recommendations__recommendation_id__replace_draft_media_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `recommendation_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ReplaceDraftMediaBody
- `photo_ids` · string (uuid)[] · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads sets
7 endpoints. HTML: https://developers.keystone.app/api/console/ads-sets/
### GET /api/v1/admin/businesses/{business_id}/ads/ad-sets/{ad_set_id}
Get an ad set
Operation id: `get_ad_set_api_v1_admin_businesses__business_id__ads_ad_sets__ad_set_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ad_set_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AdSetData_
- `request_id` · string · required
- `success` · true
- `data` · AdSetData · required: Wire shape consumed by Console; mirrors the FE `AdSet` interface with new canonical fields alongside the old string-formatted ones.
- `id` · string (uuid) · required
- `name` · string · required
- `facebookPage` · string
- `facebookPageId` · string | null
- `destination` · "website" | "facebook-native" | "instant_form"
- `websiteUrl` · string
- `dailyBudget` · string
- `dailyBudgetMinor` · integer | null
- `location` · string
- `radius` · string
- `radiusMeters` · integer | null
- `units` · AdUnitData[]
- `currencyCode` · string
- `isDraft` · boolean
- `externalPlatformAdsetId` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/ads/ad-sets/{ad_set_id}
Patch ad set fields (mutable; audit-logged)
Operation id: `patch_ad_set_api_v1_admin_businesses__business_id__ads_ad_sets__ad_set_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ad_set_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AdSetPatchBody
- `name` · string | null
- `facebookPageId` · string | null
- `destination` · "website" | "facebook-native" | "instant_form" | null
- `destinationUrl` · string | null
- `dailyBudgetMinor` · integer | null
- `addressLine` · string | null
- `lat` · number | null
- `lng` · number | null
- `radiusMeters` · integer | null
**Responses**
- `200` Successful Response: SuccessResponse_AdSetData_
- `request_id` · string · required
- `success` · true
- `data` · AdSetData · required: Wire shape consumed by Console; mirrors the FE `AdSet` interface with new canonical fields alongside the old string-formatted ones.
- `id` · string (uuid) · required
- `name` · string · required
- `facebookPage` · string
- `facebookPageId` · string | null
- `destination` · "website" | "facebook-native" | "instant_form"
- `websiteUrl` · string
- `dailyBudget` · string
- `dailyBudgetMinor` · integer | null
- `location` · string
- `radius` · string
- `radiusMeters` · integer | null
- `units` · AdUnitData[]
- `currencyCode` · string
- `isDraft` · boolean
- `externalPlatformAdsetId` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/ads/ad-sets/{ad_set_id}
Soft-delete ad set
Operation id: `delete_ad_set_api_v1_admin_businesses__business_id__ads_ad_sets__ad_set_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ad_set_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/ad-sets/{ad_set_id}/generate-ad-copy
DEPRECATED: LLM-generate ad copy (use the unit-level generate-copy)
Operation id: `generate_ad_copy_api_v1_admin_businesses__business_id__ads_ad_sets__ad_set_id__generate_ad_copy_post`
DEPRECATED (KS-2013): copy is per-ad. Delegates to the set's FIRST
unit; 409 ``AD_SET_HAS_NO_UNITS`` when the set has none. Use
``POST /businesses/{b}/ads/units/{unit_id}/generate-copy`` instead.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ad_set_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AdCopyGenerationData_
- `request_id` · string · required
- `success` · true
- `data` · AdCopyGenerationData · required: Response body for ``POST /ad-sets/{id}/generate-ad-copy``.
- `adCopy` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/ad-sets
List ad sets in a campaign
Operation id: `list_ad_sets_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__ad_sets_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AdSetData__
- `request_id` · string · required
- `success` · true
- `data` · AdSetData[] · required
- `id` · string (uuid) · required
- `name` · string · required
- `facebookPage` · string
- `facebookPageId` · string | null
- `destination` · "website" | "facebook-native" | "instant_form"
- `websiteUrl` · string
- `dailyBudget` · string
- `dailyBudgetMinor` · integer | null
- `location` · string
- `radius` · string
- `radiusMeters` · integer | null
- `units` · AdUnitData[]
- `currencyCode` · string
- `isDraft` · boolean
- `externalPlatformAdsetId` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/campaigns/{campaign_id}/ad-sets
Create an ad set
Operation id: `create_ad_set_api_v1_admin_businesses__business_id__ads_campaigns__campaign_id__ad_sets_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `campaign_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AdSetCreateBody
- `name` · string | null
- `facebookPageId` · string | null
- `destination` · "website" | "facebook-native" | "instant_form"
- `destinationUrl` · string | null
- `dailyBudgetMinor` · integer | null
- `addressLine` · string | null
- `lat` · number | null
- `lng` · number | null
- `radiusMeters` · integer | null
- `currencyCode` · string
**Responses**
- `200` Successful Response: SuccessResponse_AdSetData_
- `request_id` · string · required
- `success` · true
- `data` · AdSetData · required: Wire shape consumed by Console; mirrors the FE `AdSet` interface with new canonical fields alongside the old string-formatted ones.
- `id` · string (uuid) · required
- `name` · string · required
- `facebookPage` · string
- `facebookPageId` · string | null
- `destination` · "website" | "facebook-native" | "instant_form"
- `websiteUrl` · string
- `dailyBudget` · string
- `dailyBudgetMinor` · integer | null
- `location` · string
- `radius` · string
- `radiusMeters` · integer | null
- `units` · AdUnitData[]
- `currencyCode` · string
- `isDraft` · boolean
- `externalPlatformAdsetId` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/generate-ad-copy
LLM-generate ad copy from wizard local state (no persistence)
Operation id: `generate_ad_copy_pre_draft_api_v1_admin_businesses__business_id__ads_generate_ad_copy_post`
Pre-draft variant of ``generate-ad-copy``. Used by the New
Campaign wizard before any ad set row exists — same generation
logic, same business-profile grounding, just sourced from the
request body instead of a DB read. No row writes.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AdCopyPreDraftBody
- `destination` · "website" | "facebook-native" | "instant_form" | null
- `destinationUrl` · string | null
- `addressLine` · string | null
- `lat` · number | null
- `lng` · number | null
- `radiusMeters` · integer | null
- `dailyBudgetMinor` · integer | null
- `currencyCode` · string | null
- `campaignName` · string | null
- `userPrompt` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_AdCopyGenerationData_
- `request_id` · string · required
- `success` · true
- `data` · AdCopyGenerationData · required: Response body for ``POST /ad-sets/{id}/generate-ad-copy``.
- `adCopy` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads sync
2 endpoints. HTML: https://developers.keystone.app/api/console/ads-sync/
### POST /api/v1/admin/businesses/{business_id}/ads/accounts/{ads_account_id}/sync
Sync this ad account from Meta now (entities + insights)
Operation id: `sync_account_api_v1_admin_businesses__business_id__ads_accounts__ads_account_id__sync_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ads_account_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AdAccountData_
- `request_id` · string · required
- `success` · true
- `data` · AdAccountData · required
- `id` · string (uuid) · required
- `businessId` · string (uuid) · required
- `externalPlatform` · string · required
- `externalPlatformAccountId` · string · required
- `externalPlatformBusinessId` · string | null
- `defaultPageId` · string | null
- `currencyCode` · string · required
- `timezoneName` · string · required
- `connectedAt` · string (date-time) · required
- `lastValidatedAt` · string (date-time) | null
- `revokedAt` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/ads/accounts/{ads_account_id}/sync-attempts
List sync attempts for an ad account
Operation id: `list_sync_attempts_api_v1_admin_businesses__business_id__ads_accounts__ads_account_id__sync_attempts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ads_account_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AdSyncAttemptData__
- `request_id` · string · required
- `success` · true
- `data` · AdSyncAttemptData[] · required
- `id` · string (uuid) · required
- `businessId` · string (uuid) · required
- `adsAccountId` · string (uuid) · required
- `campaignId` · string (uuid) | null
- `syncType` · "full" | "entities" | "insights" | "insights_daily" · required
- `status` · "pending" | "running" | "partial" | "failed" | "succeeded" · required
- `attemptNumber` · integer · required
- `parentAttemptId` · string (uuid) | null
- `startedAt` · string (date-time) | null
- `finishedAt` · string (date-time) | null
- `error` · object | null
- `createdAt` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Ads units
5 endpoints. HTML: https://developers.keystone.app/api/console/ads-units/
### POST /api/v1/admin/businesses/{business_id}/ads/ad-sets/{ad_set_id}/units
Create an ad unit (creative + unit row, single transaction)
Operation id: `create_ad_unit_api_v1_admin_businesses__business_id__ads_ad_sets__ad_set_id__units_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `ad_set_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AdUnitCreateBody
- `photoIds` · string (uuid)[] | null
- `photoId` · string (uuid) | null
- `enabled` · boolean
- `displayIndex` · integer | null
- `name` · string | null
- `primaryText` · string | null
- `headline` · string | null
- `description` · string | null
- `destinationUrl` · string | null
- `callToActionType` · string | null
- `urlParams` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_AdUnitData_
- `request_id` · string · required
- `success` · true
- `data` · AdUnitData · required
- `id` · string (uuid) · required
- `name` · string · required
- `primaryText` · string | null
- `headline` · string | null
- `description` · string | null
- `destinationUrl` · string | null
- `callToActionType` · string | null
- `urlParams` · string | null
- `effectiveHeadline` · string
- `effectivePrimaryText` · string
- `effectiveDescription` · string
- `effectiveCta` · string
- `effectiveLink` · string
- `imageUrls` · string[]
- `status` · "leading" | "neutral" | "lagging" | "off"
- `enabled` · boolean
- `isDraft` · boolean
- `externalPlatformAdId` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/ads/units/{unit_id}
Toggle, swap photos, reorder
Operation id: `patch_ad_unit_api_v1_admin_businesses__business_id__ads_units__unit_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `unit_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AdUnitPatchBody
- `enabled` · boolean | null
- `photoIds` · string (uuid)[] | null
- `photoId` · string (uuid) | null
- `displayIndex` · integer | null
- `name` · string | null
- `primaryText` · string | null
- `headline` · string | null
- `description` · string | null
- `destinationUrl` · string | null
- `callToActionType` · string | null
- `urlParams` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_AdUnitData_
- `request_id` · string · required
- `success` · true
- `data` · AdUnitData · required
- `id` · string (uuid) · required
- `name` · string · required
- `primaryText` · string | null
- `headline` · string | null
- `description` · string | null
- `destinationUrl` · string | null
- `callToActionType` · string | null
- `urlParams` · string | null
- `effectiveHeadline` · string
- `effectivePrimaryText` · string
- `effectiveDescription` · string
- `effectiveCta` · string
- `effectiveLink` · string
- `imageUrls` · string[]
- `status` · "leading" | "neutral" | "lagging" | "off"
- `enabled` · boolean
- `isDraft` · boolean
- `externalPlatformAdId` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/ads/units/{unit_id}
Soft-delete an ad unit
Operation id: `delete_ad_unit_api_v1_admin_businesses__business_id__ads_units__unit_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `unit_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/units/{unit_id}/duplicate
Duplicate an ad unit (photos + copy overrides; clone starts disabled)
Operation id: `duplicate_ad_unit_api_v1_admin_businesses__business_id__ads_units__unit_id__duplicate_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `unit_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `201` Successful Response: SuccessResponse_AdUnitData_
- `request_id` · string · required
- `success` · true
- `data` · AdUnitData · required
- `id` · string (uuid) · required
- `name` · string · required
- `primaryText` · string | null
- `headline` · string | null
- `description` · string | null
- `destinationUrl` · string | null
- `callToActionType` · string | null
- `urlParams` · string | null
- `effectiveHeadline` · string
- `effectivePrimaryText` · string
- `effectiveDescription` · string
- `effectiveCta` · string
- `effectiveLink` · string
- `imageUrls` · string[]
- `status` · "leading" | "neutral" | "lagging" | "off"
- `enabled` · boolean
- `isDraft` · boolean
- `externalPlatformAdId` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/ads/units/{unit_id}/generate-copy
LLM-generate primary text for one ad unit (KS-2013 per-ad copy)
Operation id: `generate_unit_copy_api_v1_admin_businesses__business_id__ads_units__unit_id__generate_copy_post`
Unit-scoped successor of ``POST /ad-sets/{id}/generate-ad-copy``:
grounded in the unit's creative (existing primary text, CTA, link)
plus its set/campaign/business context. Same response shape — the FE
drops the text into the unit's primary-text field.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `unit_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AdCopyGenerationData_
- `request_id` · string · required
- `success` · true
- `data` · AdCopyGenerationData · required: Response body for ``POST /ad-sets/{id}/generate-ad-copy``.
- `adCopy` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: AI providers
6 endpoints. HTML: https://developers.keystone.app/api/console/ai-providers/
### GET /api/v1/admin/ai/providers
List AI provider configs
Operation id: `list_providers_api_v1_admin_ai_providers_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AiProviderData__
- `request_id` · string · required
- `success` · true
- `data` · AiProviderData[] · required
- `id` · string · required
- `name` · string · required
- `provider` · string · required
- `model` · string · required
- `max_tokens` · integer · required
- `temperature` · number · required
- `cache_enabled` · boolean · required
- `metadata` · object
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/ai/providers/{name}
Get AI provider config by name
Operation id: `get_provider_api_v1_admin_ai_providers__name__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AiProviderData_
- `request_id` · string · required
- `success` · true
- `data` · AiProviderData · required: Response shape for /ai/providers.
- `id` · string · required
- `name` · string · required
- `provider` · string · required
- `model` · string · required
- `max_tokens` · integer · required
- `temperature` · number · required
- `cache_enabled` · boolean · required
- `metadata` · object
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/ai/providers/{name}
Update AI provider config
Operation id: `patch_provider_api_v1_admin_ai_providers__name__patch`
Partial update keyed by `name`. Cache invalidates immediately for this process; other replicas pick up changes within ~60s.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AiProviderPatchBody
- `provider` · string | null
- `model` · string | null
- `max_tokens` · integer | null
- `temperature` · number | null
- `cache_enabled` · boolean | null
- `metadata` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_AiProviderData_
- `request_id` · string · required
- `success` · true
- `data` · AiProviderData · required: Response shape for /ai/providers.
- `id` · string · required
- `name` · string · required
- `provider` · string · required
- `model` · string · required
- `max_tokens` · integer · required
- `temperature` · number · required
- `cache_enabled` · boolean · required
- `metadata` · object
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/ai/providers
List AI provider configs
Operation id: `list_providers_api_v1_ai_providers_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AiProviderData__
- `request_id` · string · required
- `success` · true
- `data` · AiProviderData[] · required
- `id` · string · required
- `name` · string · required
- `provider` · string · required
- `model` · string · required
- `max_tokens` · integer · required
- `temperature` · number · required
- `cache_enabled` · boolean · required
- `metadata` · object
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/ai/providers/{name}
Get AI provider config by name
Operation id: `get_provider_api_v1_ai_providers__name__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AiProviderData_
- `request_id` · string · required
- `success` · true
- `data` · AiProviderData · required: Response shape for /ai/providers.
- `id` · string · required
- `name` · string · required
- `provider` · string · required
- `model` · string · required
- `max_tokens` · integer · required
- `temperature` · number · required
- `cache_enabled` · boolean · required
- `metadata` · object
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/ai/providers/{name}
Update AI provider config
Operation id: `patch_provider_api_v1_ai_providers__name__patch`
Partial update keyed by `name`. Cache invalidates immediately for this process; other replicas pick up changes within ~60s.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AiProviderPatchBody
- `provider` · string | null
- `model` · string | null
- `max_tokens` · integer | null
- `temperature` · number | null
- `cache_enabled` · boolean | null
- `metadata` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_AiProviderData_
- `request_id` · string · required
- `success` · true
- `data` · AiProviderData · required: Response shape for /ai/providers.
- `id` · string · required
- `name` · string · required
- `provider` · string · required
- `model` · string · required
- `max_tokens` · integer · required
- `temperature` · number · required
- `cache_enabled` · boolean · required
- `metadata` · object
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Billing
11 endpoints. HTML: https://developers.keystone.app/api/console/billing/
### GET /api/v1/admin/billing/{business_id}/auto-reload
Get a business's auto-reload config
Operation id: `get_auto_reload_api_v1_admin_billing__business_id__auto_reload_get`
Read auto-reload settings + spent-this-month / failure counters. Payments returns a default
disabled config when unset; a payments outage degrades to 503.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AutoReloadData_
- `request_id` · string · required
- `success` · true
- `data` · AutoReloadData · required: A business's auto-reload settings + counters. Money fields are **minor units**; ``trigger_credits`` is a decimal string (credits). The console shows ``spent_this_month`` and a paused/needs-attention state from ``consecutive_failures``.
- `enabled` · boolean · required
- `reload_amount` · integer | null
- `trigger_credits` · string | null
- `monthly_cap_amount` · integer | null
- `spent_this_month` · integer
- `consecutive_failures` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/billing/{business_id}/auto-reload
Save a business's auto-reload config
Operation id: `put_auto_reload_api_v1_admin_billing__business_id__auto_reload_put`
Save auto-reload config. Real membership required — this standing authority charges the
business's card unattended, so it is not something support sets on their behalf. A payments
business 4xx is surfaced verbatim (``_api_error``); transport / 5xx → 503.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AutoReloadUpdateBody
- `enabled` · boolean · required
- `reload_amount` · integer | null
- `trigger_credits` · string | null
- `monthly_cap_amount` · integer | null
**Responses**
- `200` Successful Response: SuccessResponse_AutoReloadData_
- `request_id` · string · required
- `success` · true
- `data` · AutoReloadData · required: A business's auto-reload settings + counters. Money fields are **minor units**; ``trigger_credits`` is a decimal string (credits). The console shows ``spent_this_month`` and a paused/needs-attention state from ``consecutive_failures``.
- `enabled` · boolean · required
- `reload_amount` · integer | null
- `trigger_credits` · string | null
- `monthly_cap_amount` · integer | null
- `spent_this_month` · integer
- `consecutive_failures` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/billing/{business_id}/balance
Get a business's credit balance (used-of-total)
Operation id: `get_billing_balance_api_v1_admin_billing__business_id__balance_get`
Credit balance for the used-of-total bar. The console aggregates the lots (Σ usage / Σ amount);
no per-lot breakdown is rendered in A3.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BillingBalanceData_
- `request_id` · string · required
- `success` · true
- `data` · BillingBalanceData · required: A business's credit balance. The console aggregates the lots to used-of-total (Σ usage / Σ amount) — no per-lot breakdown is rendered in A3.
- `spendable_credits` · string · required
- `debt_credits` · string · required
- `lots` · BillingBalanceLot[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/billing/{business_id}/checkout-session
Mint a subscription Checkout session URL
Operation id: `create_checkout_session_api_v1_admin_billing__business_id__checkout_session_post`
Mint a Stripe Checkout URL to subscribe. Gated at ``user`` (a business write — excludes read-only
``viewer`` members; ``manager``/``developer``/``admin`` are Keystone global staff). No subscription
gate, so a not-subscribed member can actually subscribe. ``email``/``name`` label the Stripe
customer.
**Real membership required** (``staff_bypass=False``, 2026-09-03) — staff do *not* bypass it here
as they do elsewhere. This is the mint that creates the business's Stripe customer, once,
from ``body.email``; a staff-initiated subscribe would pin a Keystone address as the customer's
permanent billing contact and divert every receipt and dunning email to it.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CheckoutSessionBody
- `plan_name` · string · required
- `email` · string (email) · required
- `name` · string | null
- `success_url` · string · required
- `cancel_url` · string · required
- `promotion_code_id` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_CheckoutSessionData_
- `request_id` · string · required
- `success` · true
- `data` · CheckoutSessionData · required
- `checkout_url` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/billing/{business_id}/grants
Grant credits to a subscribed business (platform admin)
Operation id: `grant_credits_api_v1_admin_billing__business_id__grants_post`
Grant credits to a business that has a subscription (credit-grants-admin-design.md G2, G9–G11).
"Subscribed" means *has a subscription* — `active`, `grace`, `suspended` or `canceled` — not *is
currently spendable*: a make-good to a lapsed customer is the case support hits most, and their credits
do not vanish when they lapse. payments itself will grant to any id, so this is BFF policy, like
`onboard-free`'s gate. The check runs **before** payments is called, so a refusal costs one read.
`retryable` (payments unreadable) answers **503**, and so does `status == "unknown"` on its own: it
travels with `retryable` on every unreadable path but one (a 200 whose body lacks `status` is defaulted
to `unknown` without it), and neither may fall through to a grant. The two checks are independent — an
unreadable status is never spelled `none`, so their order does not matter (verified by mutation,
2026-09-21); what matters is that `unknown` is named explicitly. Then `none` → **409 `NOT_SUBSCRIBED`**
— 409, not 402: the console reads 402 as "pay to proceed", the wrong instruction for staff acting on
someone else's account.
No sor-side business lookup, unlike `onboard-free`: a business sor does not know has no billing account,
reads as `none`, and is refused here — and payments cannot orphan anything because it refuses without an
account. `actor` is derived from the JWT; `source` is always `manual`; no `price_book_id` is sent (G4).
payments' business answers pass through verbatim with `details`: **409 `GRANT_AMOUNT_MISMATCH`** (the
same key with a different amount — `details.lot_id` / `details.granted`), **400
`NO_PRICE_BOOK_FOR_GRANT`** (a canceled business whose lots have all expired), **400 `VALIDATION_ERROR`**.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): GrantBody
- `credits` · string · required
- `reason` · string · required
- `idempotency_key` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_GrantData_
- `request_id` · string · required
- `success` · true
- `data` · GrantData · required: The lot minted — or, on a replay of the same key, the lot minted the first time — and `granted` as the caller spelled it.
- `lot_id` · string (uuid) · required
- `granted` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/billing/{business_id}/onboard-free
Put a business on the $0 free plan (platform admin)
Operation id: `onboard_free_plan_api_v1_admin_billing__business_id__onboard_free_post`
Create an `active` account and a `$0` subscription for a business that has never been billed —
no Stripe object, no credit grant, usage rates to 0.
`plan_name` says which free plan — the catalogue can carry more than one, so the card the admin
pressed is the plan that gets attached. `actor` is *not* in the body on purpose: it is derived from
the caller's JWT, so the audit row on payments cannot be spoofed or left blank by whoever calls this.
The plan is validated by payments, not here: it owns the catalogue, and duplicating the `free`-kind
check in the BFF would mean two places to keep right.
Payments' business answers pass through verbatim: **409** the business already has a billing account
(naming the plan it is on) or has reached Stripe, **404** no such active plan, **400** the named plan
is not a free one. Separately **404** when sor has no such business — payments has no `businesses` table and
deliberately no foreign key to one, so it would happily onboard a typo'd id and leave an orphan
billing account nothing would ever surface. sor owns that table; the check belongs here.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): OnboardFreeBody
- `plan_name` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_OnboardFreeData_
- `request_id` · string · required
- `success` · true
- `data` · OnboardFreeData · required: Result of putting a business on the `$0` free plan (platform-admin action).
- `business_id` · string (uuid) · required
- `status` · string · required
- `plan_name` · string · required
- `plan_kind` · string · required
- `subscription_id` · string (uuid) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/billing/{business_id}/plans
List the self-serve plan catalogue
Operation id: `get_plan_catalog_api_v1_admin_billing__business_id__plans_get`
The plan catalogue for the not-subscribed Billing screen (one card per plan). The catalogue is
global; ``business_id`` scopes membership auth only (like the other billing reads). A payments
outage degrades to 503 ``BILLING_UNAVAILABLE`` — the console just can't render cards.
**A platform admin also gets the `$0` free plan**, because only they can put a business on it
(``POST …/onboard-free`` below). The flag is derived from the caller's role and is deliberately
**not** a query parameter: the free plan has no Stripe price, so a customer who saw that card and
pressed Subscribe would get a 503 — and it sorts first, being cheapest. Making it client-settable
would put that failure one crafted request away.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PlanCatalogData_
- `request_id` · string · required
- `success` · true
- `data` · PlanCatalogData · required: The plan catalogue shown on the not-subscribed Billing screen (one card per plan). Contains the `$0` free plan **only** for a platform admin — see the route. `promotions` is the discount codes this business may redeem, as one flat list rather than a copy nested under each plan: a code that applies to several plans would otherwise be repeated, and the console has to filter by name anyway. It is `[]` when there are none **and** when the offers call fails — the cards matter more than the dropdown, so the catalogue never fails on their account.
- `plans` · PlanCatalogItem[] · required
- `promotions` · PromotionOfferData[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/billing/{business_id}/portal-session
Mint a Customer Portal session URL
Operation id: `create_portal_session_api_v1_admin_billing__business_id__portal_session_post`
Mint a Stripe Customer Portal URL to manage the subscription / payment method. Gated at ``user``
(a business write) — same tier as subscribing, and the same real-membership requirement: the Portal
lets the holder change the card and cancel, which is the business's call, not support's.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PortalSessionBody
- `return_url` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_PortalSessionData_
- `request_id` · string · required
- `success` · true
- `data` · PortalSessionData · required
- `portal_url` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/billing/{business_id}/reactivation-link
Mint the reactivation payment link for a delinquent subscription
Operation id: `create_reactivation_link_api_v1_admin_billing__business_id__reactivation_link_post`
The hosted-invoice URL whose payment revives a grace/suspended subscription — payments resolves
the sub's LITERAL latest invoice (finalizing it first when dormant cycles left it a draft; door 1,
2026-08). Payments' real 4xx (account not delinquent / latest invoice unpayable) passes through
verbatim so the console can render it.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ReactivationLinkData_
- `request_id` · string · required
- `success` · true
- `data` · ReactivationLinkData · required: The hosted-invoice URL whose payment revives a delinquent (grace/suspended) subscription.
- `reactivation_url` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/billing/{business_id}/recharge-session
Mint a one-time credit-recharge Checkout session URL
Operation id: `create_recharge_session_api_v1_admin_billing__business_id__recharge_session_post`
Mint a recharge Checkout URL (``amount`` = minor units; payments enforces $5–$10k + requires an
in-force subscription). Real membership required — a support click here charges the customer's
saved card for real. A payments **402** (no subscription) / **400** (bad amount) is surfaced
verbatim; transport / 5xx → 503.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): RechargeSessionBody
- `amount` · integer · required
- `success_url` · string · required
- `cancel_url` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_RechargeSessionData_
- `request_id` · string · required
- `success` · true
- `data` · RechargeSessionData · required
- `checkout_url` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/billing/{business_id}/status
Get a business's billing/entitlement status
Operation id: `get_billing_status_api_v1_admin_billing__business_id__status_get`
Read plan + entitlement for the Billing overview. ``get_access_status`` is fail-closed for the
enforcement gate, but for *display* an unreadable status must not masquerade as suspended — so a
``retryable`` (payments-unreachable) result surfaces as 503, not a fake status.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BillingStatusData_
- `request_id` · string · required
- `success` · true
- `data` · BillingStatusData · required: A business's entitlement snapshot for the console. ``access`` is true only for active/grace; the console reads ``status`` for the grace/suspended/must-subscribe UX and ``plan_name`` / ``current_period_end`` for the overview.
- `status` · string · required
- `access` · boolean · required
- `ops_blocked` · boolean
- `plan_name` · string | null
- `plan_kind` · string | null
- `current_period_end` · string | null
- `cancel_at_period_end` · boolean
- `grace_days_remaining` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Blog agent
2 endpoints. HTML: https://developers.keystone.app/api/console/blog-agent/
### GET /api/v1/businesses/{business_id}/blog-agent/config
Get effective blog-agent settings for a business
Operation id: `get_config_api_v1_businesses__business_id__blog_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BlogAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · BlogAgentConfigData · required: Blog-agent settings — custom instructions only (scheduler is separate).
- `business_id` · string · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/blog-agent/config
Update blog-agent settings for a business
Operation id: `patch_config_api_v1_businesses__business_id__blog_agent_config_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BlogAgentConfigPatchBody
- `instructions` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_BlogAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · BlogAgentConfigData · required: Blog-agent settings — custom instructions only (scheduler is separate).
- `business_id` · string · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Blog auto schedule
3 endpoints. HTML: https://developers.keystone.app/api/console/blog-auto-schedule/
### GET /api/v1/businesses/{business_id}/blog_posts/auto_schedule
Get business-level auto-schedule
Operation id: `get_business_auto_schedule_api_v1_businesses__business_id__blog_posts_auto_schedule_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BlogAutoScheduleData_
- `request_id` · string · required
- `success` · true
- `data` · BlogAutoScheduleData · required
- `id` · string · required
- `business_id` · string · required
- `auto_publish` · boolean · required
- `frequency_type` · string · required
- `is_enabled` · boolean · required
- `require_approval` · boolean
- `target_word_count` · integer
- `timezone` · string
- `time_of_day` · string
- `next_run_at` · integer | null
- `last_run_at` · integer | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/businesses/{business_id}/blog_posts/auto_schedule
Create or update business-level auto-schedule
Operation id: `save_business_auto_schedule_api_v1_businesses__business_id__blog_posts_auto_schedule_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BlogAutoScheduleBody
- `auto_publish` · boolean
- `frequency_type` · string
- `is_enabled` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_BlogAutoScheduleData_
- `request_id` · string · required
- `success` · true
- `data` · BlogAutoScheduleData · required
- `id` · string · required
- `business_id` · string · required
- `auto_publish` · boolean · required
- `frequency_type` · string · required
- `is_enabled` · boolean · required
- `require_approval` · boolean
- `target_word_count` · integer
- `timezone` · string
- `time_of_day` · string
- `next_run_at` · integer | null
- `last_run_at` · integer | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/businesses/{business_id}/blog_posts/auto_schedule
Delete business-level auto-schedule
Operation id: `delete_business_auto_schedule_api_v1_businesses__business_id__blog_posts_auto_schedule_delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Blog posts
7 endpoints. HTML: https://developers.keystone.app/api/console/blog-posts/
### GET /api/v1/admin/businesses/{business_id}/blog_posts
List blog posts for a business
Operation id: `list_blog_posts_api_v1_admin_businesses__business_id__blog_posts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string | no | Status bucket filter: all\|draft\|published\|pending\|archived |
| `search` | query | string \| null | no | Search title and excerpt |
| `cursor` | query | string \| null | no | Pagination cursor |
| `limit` | query | integer | no | Page size |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_BlogPostListItemData__
- `request_id` · string · required
- `success` · true
- `data` · BlogPostListItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `thumbnail_photo_id` · string | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `excerpt` · string | null
- `is_featured` · boolean
- `media_status` · string | null
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/blog_posts
Create a blog post
Operation id: `create_blog_post_api_v1_admin_businesses__business_id__blog_posts_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BlogPostWriteBody
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[]
- `photo_ids` · string[]
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[]
- `author_team_member_ids` · string[]
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
**Responses**
- `201` Successful Response: SuccessResponse_BlogPostData_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostData · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[] | null
- `photo_ids` · string[] | null
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[] | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `authors` · BlogPostAuthorData[]
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/blog_posts/{blog_post_id}
Get a single blog post
Operation id: `get_blog_post_api_v1_admin_businesses__business_id__blog_posts__blog_post_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `blog_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BlogPostData_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostData · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[] | null
- `photo_ids` · string[] | null
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[] | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `authors` · BlogPostAuthorData[]
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/blog_posts/{blog_post_id}
Update a blog post
Operation id: `update_blog_post_api_v1_admin_businesses__business_id__blog_posts__blog_post_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `blog_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BlogPostWriteBody
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[]
- `photo_ids` · string[]
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[]
- `author_team_member_ids` · string[]
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_BlogPostData_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostData · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[] | null
- `photo_ids` · string[] | null
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[] | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `authors` · BlogPostAuthorData[]
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/blog_posts/{blog_post_id}
Delete a blog post
Operation id: `delete_blog_post_api_v1_admin_businesses__business_id__blog_posts__blog_post_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `blog_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/blog_posts/generate
Generate a blog post using AI
Operation id: `generate_blog_post_endpoint_api_v1_admin_businesses__business_id__blog_posts_generate_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string \| null | no | |
| `X-Workflow-Bypass` | header | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BlogPostGenerateRequest
- `prompt` · string · required: User prompt describing the blog post to generate
- `provider` · string | null: Optional AI provider override: openai, anthropic, gemini
- `new_blog_candidate_id` · string (uuid) | null: A new-blog candidate id (blog_suggestions, scope new_post); its brief drives the post
**Responses**
- `200` Successful Response: SuccessResponse_BlogPostGenerateResponse_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostGenerateResponse · required
- `title` · string · required
- `slug` · string · required
- `excerpt` · string · required
- `content_markdown` · string · required
- `tags` · string[]
- `media_brief` · object: Hero-photo brief for the workflow worker to resolve against the media library. Empty unless blog_media_brief_enabled is on.
- `body_images` · object[]: Inline photo briefs, each paired with a [[ks-image:N]] marker in content_markdown. Empty unless blog_body_images_enabled is on.
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[]
- `brief` · object | null
- `metadata` · BlogPostGenerateMetadata · required
- `generation_status` · string
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `blog_post` · BlogPostData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/blog_posts/generate/stream
Stream blog generation status events (SSE)
Operation id: `stream_blog_generate_api_v1_admin_businesses__business_id__blog_posts_generate_stream_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `workflow_run_id` | query | string | yes | Workflow run id returned by generate endpoint |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Business
6 endpoints. HTML: https://developers.keystone.app/api/console/business/
### POST /api/v1/admin/businesses
Create business
Operation id: `create_business_api_v1_admin_businesses_post`
Create a business. company_name required; id optional (server generates if omitted).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BusinessCreateBody
- `company_name` · string · required
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `descriptions` · Descriptions | null
- `company_description` · string | null
- `about` · string | null
- `mission_statement` · string | null
- `values` · string[]
- `contact_info` · ContactInfo | null
- `primary_phone` · string | null
- `primary_email` · string | null
- `support_email` · string | null
- `sales_email` · string | null
- `external_management_url` · string | null
- `social_profiles` · SocialProfiles | null
- `facebook` · string | null
- `instagram` · string | null
- `twitter` · string | null
- `linkedin` · string | null
- `youtube` · string | null
- `tiktok` · string | null
- `google_my_business` · string | null
- `pinterest` · string | null
- `yelp` · string | null
- `tripadvisor` · string | null
- `google_review` · string | null
- `industries` · BusinessIndustries | null
- `primary` · string | null
- `secondary` · string[]
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean
**Responses**
- `201` Successful Response: SuccessResponse_BusinessProfileData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessProfileData · required: Business profile for GET response.
- `id` · string · required
- `company_name` · string | null
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `descriptions` · Descriptions | null
- `contact_info` · ContactInfo | null
- `social_profiles` · SocialProfiles | null
- `industries` · BusinessIndustries | null
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean
- `timezone` · string
- `timezone_source` · string
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}
Get business details
Operation id: `get_business_api_v1_admin_businesses__business_id__get`
Get business by id. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessProfileData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessProfileData · required: Business profile for GET response.
- `id` · string · required
- `company_name` · string | null
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `descriptions` · Descriptions | null
- `contact_info` · ContactInfo | null
- `social_profiles` · SocialProfiles | null
- `industries` · BusinessIndustries | null
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean
- `timezone` · string
- `timezone_source` · string
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}
Update specific fields of business profile
Operation id: `patch_business_api_v1_admin_businesses__business_id__patch`
Partial update (PATCH). 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BusinessUpdateBody
- `company_name` · string | null
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `descriptions` · Descriptions | null
- `company_description` · string | null
- `about` · string | null
- `mission_statement` · string | null
- `values` · string[]
- `contact_info` · ContactInfo | null
- `primary_phone` · string | null
- `primary_email` · string | null
- `support_email` · string | null
- `sales_email` · string | null
- `external_management_url` · string | null
- `social_profiles` · SocialProfiles | null
- `facebook` · string | null
- `instagram` · string | null
- `twitter` · string | null
- `linkedin` · string | null
- `youtube` · string | null
- `tiktok` · string | null
- `google_my_business` · string | null
- `pinterest` · string | null
- `yelp` · string | null
- `tripadvisor` · string | null
- `google_review` · string | null
- `industries` · BusinessIndustries | null
- `primary` · string | null
- `secondary` · string[]
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean | null
- `timezone` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_BusinessProfileData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessProfileData · required: Business profile for GET response.
- `id` · string · required
- `company_name` · string | null
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `descriptions` · Descriptions | null
- `contact_info` · ContactInfo | null
- `social_profiles` · SocialProfiles | null
- `industries` · BusinessIndustries | null
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean
- `timezone` · string
- `timezone_source` · string
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/api-key
Get business public API credentials (URL + key)
Operation id: `get_business_api_key_api_v1_admin_businesses__business_id__api_key_get`
Return the public API URL and active key for a business. 404 if the business doesn't exist.
The full key is returned so the console can offer copy-to-clipboard; it is never rendered in
full. ``key_prefix``/``api_key`` are null when the business has no active key.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessApiKeyData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessApiKeyData · required: Public API credentials for a business (admin account overview). ``api_url`` is the global SOR public API base URL (same for every business). ``key_prefix``/``api_key`` are null when the business has no active key yet. ``api_key`` is the full decrypted key, returned so the admin console can offer copy-to-clipboard without ever rendering it in full.
- `api_url` · string · required
- `key_prefix` · string | null
- `api_key` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/dashboard
Get dashboard info for a specific business
Operation id: `get_business_dashboard_by_id_api_v1_admin_businesses__business_id__dashboard_get`
Counts (services, locations, team, faq, jobs, photos) for a specific business.
Access control is handled by ``auth_admin("viewer")``: the caller must have a ``business_users``
row for ``business_id``, OR carry ``global_business_access`` (super-admins). Otherwise 403.
Returns 404 NOT_FOUND when the business id doesn't exist.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessDashboardData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessDashboardData · required: Dashboard summary for the authenticated user's business.
- `business_id` · string · required
- `business_name` · string · required
- `services_count` · integer · required
- `packages_count` · integer · required
- `service_items_count` · integer · required
- `locations_count` · integer · required
- `team_count` · integer · required
- `faq_count` · integer · required
- `jobs_count` · integer · required
- `photos_count` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/dashboard
[Deprecated] Dashboard info for authenticated user's business — use /businesses/{business_id}/dashboard (deprecated)
Operation id: `get_business_dashboard_api_v1_admin_businesses_dashboard_get`
Get business name and counts (services, locations, team, faq, jobs, photos) for the user's *own* business.
Returns 404 NOT_FOUND when the caller has no ``business_users`` row — including for admins who
aren't directly attached to any business. Use ``GET /businesses/{business_id}/dashboard`` to
fetch counts for a specific business (with the standard business-access auth check).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessDashboardData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessDashboardData · required: Dashboard summary for the authenticated user's business.
- `business_id` · string · required
- `business_name` · string · required
- `services_count` · integer · required
- `packages_count` · integer · required
- `service_items_count` · integer · required
- `locations_count` · integer · required
- `team_count` · integer · required
- `faq_count` · integer · required
- `jobs_count` · integer · required
- `photos_count` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Business claims
3 endpoints. HTML: https://developers.keystone.app/api/console/businesses-claim/
### GET /api/v1/admin/businesses/{business_id}/claim
Who holds this business, and whether the caller can claim or extend
Operation id: `get_claim_api_v1_admin_businesses__business_id__claim_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ClaimStateData_
- `request_id` · string · required
- `success` · true
- `data` · ClaimStateData · required: Everything the console's claim control shows.
- `business_id` · string · required
- `claim` · ClaimData | null
- `launched` · boolean · required
- `can_claim` · boolean · required
- `can_extend` · boolean · required
- `server_time` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/claim
Claim a business nobody holds, for 24 hours
Operation id: `put_claim_api_v1_admin_businesses__business_id__claim_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ClaimStateData_
- `request_id` · string · required
- `success` · true
- `data` · ClaimStateData · required: Everything the console's claim control shows.
- `business_id` · string · required
- `claim` · ClaimData | null
- `launched` · boolean · required
- `can_claim` · boolean · required
- `can_extend` · boolean · required
- `server_time` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/claim/extend
Extend the caller's claim once, by another 24 hours
Operation id: `extend_claim_api_v1_admin_businesses__business_id__claim_extend_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ClaimStateData_
- `request_id` · string · required
- `success` · true
- `data` · ClaimStateData · required: Everything the console's claim control shows.
- `business_id` · string · required
- `claim` · ClaimData | null
- `launched` · boolean · required
- `can_claim` · boolean · required
- `can_extend` · boolean · required
- `server_time` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Contacts
45 endpoints. HTML: https://developers.keystone.app/api/console/contacts/
### GET /api/v1/admin/businesses/{business_id}/contacts
List contacts
Operation id: `list_contacts_api_v1_admin_businesses__business_id__contacts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | Filter by signal: hot,warm,cool,new,unassigned |
| `touch` | query | string \| null | no | Filter by touch: responsive,slow,unresponsive,ghosted,unassigned |
| `stage` | query | string \| null | no | Filter by stage: new,engaged,booked,returning,lapsed,lost,unassigned |
| `needs_attention` | query | boolean \| null | no | Filter contacts requiring supervisory attention |
| `identity_resolution_status` | query | string \| null | no | |
| `include_deleted` | query | boolean | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `sort` | query | string | no | Sort order. 'last_activity' (default) surfaces the most recent conversation first; also accepts any contact column (created_at, updated_at, ...). |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactData__
- `request_id` · string · required
- `success` · true
- `data` · ContactData[] · required
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts
Create contact
Operation id: `create_contact_api_v1_admin_businesses__business_id__contacts_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactCreateBody
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `email` · string | null
- `phone` · string | null
- `emails` · ContactEmailInputBody[]
- `email` · string · required
- `source` · string | null
- `is_primary` · boolean
- `phones` · ContactPhoneInputBody[]
- `phone` · string · required
- `source` · string | null
- `is_primary` · boolean
- `status` · string
- `lifecycle_stage` · string
- `metadata_` · object | null
- `source` · ContactSourceBody | null
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `consent` · ContactConsentBody | null
- `transactional_sms` · boolean | null
- `marketing_sms` · boolean | null
- `marketing_email` · boolean | null
- `tos_privacy` · boolean | null
- `ai_initiate` · boolean
**Responses**
- `201` Successful Response: SuccessResponse_ContactOutcomeData_
- `request_id` · string · required
- `success` · true
- `data` · ContactOutcomeData · required: Structured response for POST /contacts and PUT /contacts/upsert.
- `contact` · ContactData · required: Contact detail / list item response shape.
- `operation` · string · required
- `consumer_link_status` · string · required
- `duplicate_flagged` · boolean
- `identity_review_id` · string | null
- `duplicate_review_id` · string | null
- `ai_initiation` · AiInitiationResult | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}
Get contact detail
Operation id: `get_contact_api_v1_admin_businesses__business_id__contacts__contact_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/contacts/{contact_id}
Update contact
Operation id: `update_contact_api_v1_admin_businesses__business_id__contacts__contact_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactUpdateBody
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string | null
- `lifecycle_stage` · string | null
- `signal` · string | null
- `touch` · string | null
- `stage` · string | null
- `metadata_` · object | null
- `last_contacted_at` · string (date-time) | null
- `auto_contact_enabled` · boolean | null
- `outreach_suppressed` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/contacts/{contact_id}
Delete contact
Operation id: `delete_contact_api_v1_admin_businesses__business_id__contacts__contact_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/classification
Override contact classification
Operation id: `patch_classification_api_v1_admin_businesses__business_id__contacts__contact_id__classification_patch`
Override one or more classification dimensions for the contact. Any field omitted in the body is left unchanged. Each changed dimension lands a row in `contact_classification_history` with `changed_by="user:api"` and the supplied `reason`.
Manual overrides are NOT sticky — the next AI classification pass (triggered by inbound messages or DELIVERED/READ status events) may overwrite them. To preserve a manual value, take the conversation out of AUTONOMOUS AI mode.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ClassifyBody
- `signal` · string | null
- `touch` · string | null
- `stage` · string | null
- `reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/classification-history
Get classification history
Operation id: `get_classification_history_api_v1_admin_businesses__business_id__contacts__contact_id__classification_history_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `field` | query | string \| null | no | Filter by: signal, touch, stage |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ClassificationHistoryItem__
- `request_id` · string · required
- `success` · true
- `data` · ClassificationHistoryItem[] · required
- `type` · string
- `field` · string · required
- `old_value` · string | null
- `new_value` · string · required
- `changed_by` · string · required
- `reason` · string | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/classify
Manually classify a contact (legacy POST alias)
Operation id: `classify_contact_api_v1_admin_businesses__business_id__contacts__contact_id__classify_post`
Override one or more classification dimensions for the contact. Any field omitted in the body is left unchanged. Each changed dimension lands a row in `contact_classification_history` with `changed_by="user:api"` and the supplied `reason`.
Manual overrides are NOT sticky — the next AI classification pass (triggered by inbound messages or DELIVERED/READ status events) may overwrite them. To preserve a manual value, take the conversation out of AUTONOMOUS AI mode.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ClassifyBody
- `signal` · string | null
- `touch` · string | null
- `stage` · string | null
- `reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/conversation
Get this contact's conversation
Operation id: `get_contact_conversation_api_v1_admin_businesses__business_id__contacts__contact_id__conversation_get`
Returns the conversation for this contact's consumer, or `data: null` with 200 when none exists yet. 404 is reserved for the contact itself being missing from this business — the frontend should branch on `data == null`, not on status code.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_Union_ConversationData__NoneType__
- `request_id` · string · required
- `success` · true
- `data` · ConversationData | null · required
- `id` · string · required
- `consumer_id` · string | null
- `status` · string · required
- `assigned_to` · integer | null
- `ai_enabled` · boolean
- `ai_mode` · string
- `last_message_at` · string (date-time) | null
- `last_message_preview` · string | null
- `last_message_channel` · string | null
- `unread_count` · integer
- `message_count` · integer
- `snoozed_until` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `reply_channel` · ReplyChannelData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/emails
List contact emails
Operation id: `list_emails_api_v1_admin_businesses__business_id__contacts__contact_id__emails_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactEmailData__
- `request_id` · string · required
- `success` · true
- `data` · ContactEmailData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `business_id` · string · required
- `raw_input` · string | null
- `email` · string · required
- `verified_at` · string (date-time) | null
- `source` · string | null
- `is_deleted` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/emails
Add email to contact
Operation id: `add_email_api_v1_admin_businesses__business_id__contacts__contact_id__emails_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactEmailCreateBody
- `email` · string · required
- `source` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_ContactEmailAddResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactEmailAddResult · required: Response for POST /contacts/{id}/emails.
- `email` · ContactEmailData · required: Email record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/emails/{email_id}
Update contact email
Operation id: `update_email_api_v1_admin_businesses__business_id__contacts__contact_id__emails__email_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `email_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactEmailPatchBody
- `email` · string | null
- `source` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactEmailUpdateResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactEmailUpdateResult · required: Response for PATCH /contacts/{id}/emails/{email_id}. Mirrors `ContactEmailAddResult` so the console can reuse the same response handler — `identity_review_id` is set when a value change on a consumer-linked contact opens (or finds an existing) review.
- `email` · ContactEmailData · required: Email record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/emails/{email_id}
Remove email from contact
Operation id: `remove_email_api_v1_admin_businesses__business_id__contacts__contact_id__emails__email_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `email_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/emails/{email_id}/set-primary
Set primary email
Operation id: `set_primary_email_api_v1_admin_businesses__business_id__contacts__contact_id__emails__email_id__set_primary_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `email_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/insights
List contact insights
Operation id: `list_contact_insights_api_v1_admin_businesses__business_id__contacts__contact_id__insights_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `include_dismissed` | query | boolean | no | |
| `priority` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactInsightData__
- `request_id` · string · required
- `success` · true
- `data` · ContactInsightData[] · required
- `id` · string · required
- `contact_id` · string · required
- `business_id` · string · required
- `insight_type` · string · required
- `title` · string · required
- `body` · string · required
- `priority` · string · required
- `action_type` · string | null
- `action_data` · object | null
- `is_dismissed` · boolean
- `expires_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/insights/{insight_id}
Get a contact insight
Operation id: `get_contact_insight_api_v1_admin_businesses__business_id__contacts__contact_id__insights__insight_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `insight_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactInsightData_
- `request_id` · string · required
- `success` · true
- `data` · ContactInsightData · required: Contact insight response shape.
- `id` · string · required
- `contact_id` · string · required
- `business_id` · string · required
- `insight_type` · string · required
- `title` · string · required
- `body` · string · required
- `priority` · string · required
- `action_type` · string | null
- `action_data` · object | null
- `is_dismissed` · boolean
- `expires_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/insights/{insight_id}/dismiss
Dismiss a contact insight
Operation id: `dismiss_contact_insight_api_v1_admin_businesses__business_id__contacts__contact_id__insights__insight_id__dismiss_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `insight_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactInsightData_
- `request_id` · string · required
- `success` · true
- `data` · ContactInsightData · required: Contact insight response shape.
- `id` · string · required
- `contact_id` · string · required
- `business_id` · string · required
- `insight_type` · string · required
- `title` · string · required
- `body` · string · required
- `priority` · string · required
- `action_type` · string | null
- `action_data` · object | null
- `is_dismissed` · boolean
- `expires_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/needs-attention
Resolve contact needs-attention overlay
Operation id: `resolve_contact_needs_attention_api_v1_admin_businesses__business_id__contacts__contact_id__needs_attention_delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `re_enable_ai` | query | boolean | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: object
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/opt-in
Record opt-in
Operation id: `opt_in_api_v1_admin_businesses__business_id__contacts__contact_id__opt_in_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): OptInOutBody
- `channel` · string · required
- `source` · string · required
- `occurred_at` · string (date-time) | null
- `idempotency_key` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactPreferenceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactPreferenceData · required: GET /contacts/{id}/preferences response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `transactional_sms_opt_in_at` · string (date-time) | null
- `transactional_sms_opt_out_at` · string (date-time) | null
- `marketing_sms_opt_in_at` · string (date-time) | null
- `marketing_sms_opt_out_at` · string (date-time) | null
- `marketing_email_opt_in_at` · string (date-time) | null
- `marketing_email_opt_out_at` · string (date-time) | null
- `tos_privacy_accepted_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/opt-out
Record opt-out
Operation id: `opt_out_api_v1_admin_businesses__business_id__contacts__contact_id__opt_out_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): OptInOutBody
- `channel` · string · required
- `source` · string · required
- `occurred_at` · string (date-time) | null
- `idempotency_key` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactPreferenceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactPreferenceData · required: GET /contacts/{id}/preferences response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `transactional_sms_opt_in_at` · string (date-time) | null
- `transactional_sms_opt_out_at` · string (date-time) | null
- `marketing_sms_opt_in_at` · string (date-time) | null
- `marketing_sms_opt_out_at` · string (date-time) | null
- `marketing_email_opt_in_at` · string (date-time) | null
- `marketing_email_opt_out_at` · string (date-time) | null
- `tos_privacy_accepted_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/phones
List contact phones
Operation id: `list_phones_api_v1_admin_businesses__business_id__contacts__contact_id__phones_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactPhoneData__
- `request_id` · string · required
- `success` · true
- `data` · ContactPhoneData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `business_id` · string · required
- `raw_input` · string | null
- `phone` · string · required
- `phone_type` · string | null
- `country_code` · string | null
- `verified_at` · string (date-time) | null
- `source` · string | null
- `is_deleted` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/phones
Add phone to contact
Operation id: `add_phone_api_v1_admin_businesses__business_id__contacts__contact_id__phones_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactPhoneCreateBody
- `phone` · string · required
- `source` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_ContactPhoneAddResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactPhoneAddResult · required: Response for POST /contacts/{id}/phones.
- `phone` · ContactPhoneData · required: Phone record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/phones/{phone_id}
Update contact phone
Operation id: `update_phone_api_v1_admin_businesses__business_id__contacts__contact_id__phones__phone_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `phone_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactPhonePatchBody
- `phone` · string | null
- `phone_type` · string | null
- `country_code` · string | null
- `source` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactPhoneUpdateResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactPhoneUpdateResult · required: Response for PATCH /contacts/{id}/phones/{phone_id}. Mirrors `ContactPhoneAddResult` so the console can reuse the same response handler — `identity_review_id` is set when a value change on a consumer-linked contact opens (or finds an existing) review.
- `phone` · ContactPhoneData · required: Phone record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/phones/{phone_id}
Remove phone from contact
Operation id: `remove_phone_api_v1_admin_businesses__business_id__contacts__contact_id__phones__phone_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `phone_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/phones/{phone_id}/set-primary
Set primary phone
Operation id: `set_primary_phone_api_v1_admin_businesses__business_id__contacts__contact_id__phones__phone_id__set_primary_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `phone_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/preferences
Get preferences
Operation id: `get_preferences_api_v1_admin_businesses__business_id__contacts__contact_id__preferences_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactPreferenceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactPreferenceData · required: GET /contacts/{id}/preferences response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `transactional_sms_opt_in_at` · string (date-time) | null
- `transactional_sms_opt_out_at` · string (date-time) | null
- `marketing_sms_opt_in_at` · string (date-time) | null
- `marketing_sms_opt_out_at` · string (date-time) | null
- `marketing_email_opt_in_at` · string (date-time) | null
- `marketing_email_opt_out_at` · string (date-time) | null
- `tos_privacy_accepted_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/reassess
Trigger a fresh AI classification pass
Operation id: `reassess_contact_api_v1_admin_businesses__business_id__contacts__contact_id__reassess_post`
Publishes a manual `ClassificationRequest` for this contact's consumer. The classification consumer picks it up out-of-band and writes back signal/touch/stage + a fresh `latest_insight`; expect the new values to land within seconds. Returns the current contact snapshot — poll the same contact-detail endpoint to see the updated classification.
Returns 202 even when Kafka is disabled — the next inbound message will pick the contact up; the response includes the current state regardless.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `202` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/score-history
Get engagement score history
Operation id: `get_score_history_api_v1_admin_businesses__business_id__contacts__contact_id__score_history_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ScoreHistoryItem__
- `request_id` · string · required
- `success` · true
- `data` · ScoreHistoryItem[] · required
- `engagement_score` · integer · required
- `purchase_intent` · integer | null
- `model_version` · string | null
- `reasoning` · string | null
- `input_signals` · object | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/sources
List sources
Operation id: `list_sources_api_v1_admin_businesses__business_id__contacts__contact_id__sources_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactSourceData__
- `request_id` · string · required
- `success` · true
- `data` · ContactSourceData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/{contact_id}/sources
Add source
Operation id: `add_source_api_v1_admin_businesses__business_id__contacts__contact_id__sources_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactSourceCreateBody
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_ContactSourceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactSourceData · required: Source attribution response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/export
Export contacts as CSV
Operation id: `export_contacts_api_v1_admin_businesses__business_id__contacts_export_get`
Stream every matching contact as a CSV file (one server request, no client-side paging).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `q` | query | string \| null | no | Optional search query; when set, matches the search view |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | |
| `touch` | query | string \| null | no | |
| `stage` | query | string \| null | no | |
| `needs_attention` | query | boolean \| null | no | |
| `identity_resolution_status` | query | string \| null | no | |
| `include_deleted` | query | boolean | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/lookup
Lookup contact by email/phone
Operation id: `lookup_contacts_api_v1_admin_businesses__business_id__contacts_lookup_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactLookupBody
- `email` · string | null
- `phone` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactLookupResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactLookupResult · required: POST /contacts/lookup response data.
- `match_type` · string · required
- `contacts` · ContactData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contacts/merge
Merge contacts
Operation id: `merge_contacts_api_v1_admin_businesses__business_id__contacts_merge_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactMergeBody
- `surviving_contact_id` · string (uuid) · required
- `merged_contact_id` · string (uuid) · required
- `field_resolutions` · object | null
- `merge_reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/needs-attention
List contacts needing attention
Operation id: `get_needs_attention_api_v1_admin_businesses__business_id__contacts_needs_attention_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_NeedsAttentionData_
- `request_id` · string · required
- `success` · true
- `data` · NeedsAttentionData · required: GET /contacts/needs-attention response shape.
- `items` · NeedsAttentionItem[] · required
- `total` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/pipeline-summary
Get pipeline summary
Operation id: `get_pipeline_summary_api_v1_admin_businesses__business_id__contacts_pipeline_summary_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PipelineSummaryData_
- `request_id` · string · required
- `success` · true
- `data` · PipelineSummaryData · required: GET /contacts/pipeline-summary response shape.
- `summary` · string · required
- `stats` · object · required
- `generated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/search
Search contacts
Operation id: `search_contacts_api_v1_admin_businesses__business_id__contacts_search_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `q` | query | string | yes | |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | Filter by signal: hot,warm,cool,new,unassigned |
| `touch` | query | string \| null | no | Filter by touch: responsive,slow,unresponsive,ghosted,unassigned |
| `stage` | query | string \| null | no | Filter by stage: new,engaged,booked,returning,lapsed,lost,unassigned |
| `needs_attention` | query | boolean \| null | no | Filter contacts requiring supervisory attention |
| `identity_resolution_status` | query | string \| null | no | |
| `include_deleted` | query | boolean | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `sort` | query | string | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactData__
- `request_id` · string · required
- `success` · true
- `data` · ContactData[] · required
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contacts/stats
Get classification stats
Operation id: `get_classification_stats_api_v1_admin_businesses__business_id__contacts_stats_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | |
| `touch` | query | string \| null | no | |
| `stage` | query | string \| null | no | |
| `needs_attention` | query | boolean \| null | no | |
| `identity_resolution_status` | query | string \| null | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ClassificationStatsData_
- `request_id` · string · required
- `success` · true
- `data` · ClassificationStatsData · required: GET /contacts/stats response shape.
- `total` · integer · required
- `signal` · object · required
- `touch` · object · required
- `stage` · object · required
- `lifecycle_stage` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/contacts/upsert
Upsert contact
Operation id: `upsert_contact_api_v1_admin_businesses__business_id__contacts_upsert_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ContactCreateBody
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `email` · string | null
- `phone` · string | null
- `emails` · ContactEmailInputBody[]
- `email` · string · required
- `source` · string | null
- `is_primary` · boolean
- `phones` · ContactPhoneInputBody[]
- `phone` · string · required
- `source` · string | null
- `is_primary` · boolean
- `status` · string
- `lifecycle_stage` · string
- `metadata_` · object | null
- `source` · ContactSourceBody | null
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `consent` · ContactConsentBody | null
- `transactional_sms` · boolean | null
- `marketing_sms` · boolean | null
- `marketing_email` · boolean | null
- `tos_privacy` · boolean | null
- `ai_initiate` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_ContactOutcomeData_
- `request_id` · string · required
- `success` · true
- `data` · ContactOutcomeData · required: Structured response for POST /contacts and PUT /contacts/upsert.
- `contact` · ContactData · required: Contact detail / list item response shape.
- `operation` · string · required
- `consumer_link_status` · string · required
- `duplicate_flagged` · boolean
- `identity_review_id` · string | null
- `duplicate_review_id` · string | null
- `ai_initiation` · AiInitiationResult | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/duplicate-reviews
List duplicate reviews
Operation id: `list_duplicate_reviews_api_v1_admin_businesses__business_id__duplicate_reviews_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_DuplicateReviewData__
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData[] · required
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/duplicate-reviews/{review_id}
Get duplicate review
Operation id: `get_duplicate_review_api_v1_admin_businesses__business_id__duplicate_reviews__review_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_DuplicateReviewData_
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData · required: Duplicate review response shape.
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/duplicate-reviews/{review_id}/dismiss
Dismiss duplicate pair
Operation id: `dismiss_duplicate_pair_api_v1_admin_businesses__business_id__duplicate_reviews__review_id__dismiss_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): DuplicateReviewDismissBody
- `reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_DuplicateReviewData_
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData · required: Duplicate review response shape.
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/duplicate-reviews/{review_id}/merge
Merge duplicate pair
Operation id: `merge_duplicate_pair_api_v1_admin_businesses__business_id__duplicate_reviews__review_id__merge_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): DuplicateReviewMergeBody
- `surviving_contact_id` · string · required
- `field_resolutions` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_DuplicateReviewData_
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData · required: Duplicate review response shape.
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/identity-reviews
List identity reviews
Operation id: `list_identity_reviews_api_v1_admin_businesses__business_id__identity_reviews_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `business_contact_id` | query | string (uuid) \| null | no | |
| `email_matched_consumer_id` | query | string (uuid) \| null | no | |
| `phone_matched_consumer_id` | query | string (uuid) \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_IdentityReviewData__
- `request_id` · string · required
- `success` · true
- `data` · IdentityReviewData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `email_matched_consumer_id` · string | null
- `phone_matched_consumer_id` · string | null
- `conflict_snapshot` · object · required
- `status` · string · required
- `resolved_consumer_id` · string | null
- `resolution_action` · string | null
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `notes` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact` · ContactSummary | null
- `email_matched_consumer` · ConsumerSummary | null
- `phone_matched_consumer` · ConsumerSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/identity-reviews/{review_id}
Get identity review
Operation id: `get_identity_review_api_v1_admin_businesses__business_id__identity_reviews__review_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_IdentityReviewData_
- `request_id` · string · required
- `success` · true
- `data` · IdentityReviewData · required: Identity review response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `email_matched_consumer_id` · string | null
- `phone_matched_consumer_id` · string | null
- `conflict_snapshot` · object · required
- `status` · string · required
- `resolved_consumer_id` · string | null
- `resolution_action` · string | null
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `notes` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact` · ContactSummary | null
- `email_matched_consumer` · ConsumerSummary | null
- `phone_matched_consumer` · ConsumerSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/identity-reviews/{review_id}/resolve
Resolve identity review
Operation id: `resolve_identity_review_api_v1_admin_businesses__business_id__identity_reviews__review_id__resolve_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): IdentityReviewResolveBody
- `action` · "link_to_consumer" | "merge_consumers_and_link" | "create_new_consumer" | "ignore" · required
- `target_consumer_id` · string | null
- `notes` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_IdentityReviewData_
- `request_id` · string · required
- `success` · true
- `data` · IdentityReviewData · required: Identity review response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `email_matched_consumer_id` · string | null
- `phone_matched_consumer_id` · string | null
- `conflict_snapshot` · object · required
- `status` · string · required
- `resolved_consumer_id` · string | null
- `resolution_action` · string | null
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `notes` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact` · ContactSummary | null
- `email_matched_consumer` · ConsumerSummary | null
- `phone_matched_consumer` · ConsumerSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Contacts agent
2 endpoints. HTML: https://developers.keystone.app/api/console/contacts-agent/
### GET /api/v1/businesses/{business_id}/contacts-agent/config
Get effective contacts-agent settings for a business
Operation id: `get_config_api_v1_businesses__business_id__contacts_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SimpleAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SimpleAgentConfigData · required: Shared shape for contacts / ads / listings agents.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/contacts-agent/config
Update contacts-agent settings for a business
Operation id: `patch_config_api_v1_businesses__business_id__contacts_agent_config_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SimpleAgentConfigPatchBody
- `enabled` · boolean | null
- `instructions` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SimpleAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SimpleAgentConfigData · required: Shared shape for contacts / ads / listings agents.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Contexts
6 endpoints. HTML: https://developers.keystone.app/api/console/contexts/
### PUT /api/v1/admin/businesses/{business_id}/contexts/{context_type}
Save business context as new version
Operation id: `put_business_context_api_v1_admin_businesses__business_id__contexts__context_type__put`
Persist a new immutable version for the selected context type.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `context_type` | path | "business" | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BusinessContextUpdateBody
- `content` · string · required
- `source_kind` · "manual" | "generated" | "restored" | null
- `source_industry_id` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_BusinessContextCurrentData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessContextCurrentData · required: Current context payload for a business + context type.
- `id` · string · required
- `business_id` · string · required
- `context_type` · "business" · required
- `version_number` · integer · required
- `content` · string · required
- `source_kind` · "manual" | "generated" | "restored" · required
- `source_industry_id` · string | null
- `restored_from_version_id` · string | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contexts/{context_type}/current
Get current business context
Operation id: `get_current_business_context_api_v1_admin_businesses__business_id__contexts__context_type__current_get`
Get the latest saved context for the selected business and context type.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `context_type` | path | "business" | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessContextCurrentData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessContextCurrentData · required: Current context payload for a business + context type.
- `id` · string · required
- `business_id` · string · required
- `context_type` · "business" · required
- `version_number` · integer · required
- `content` · string · required
- `source_kind` · "manual" | "generated" | "restored" · required
- `source_industry_id` · string | null
- `restored_from_version_id` · string | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contexts/{context_type}/versions
List business context versions
Operation id: `list_business_context_versions_api_v1_admin_businesses__business_id__contexts__context_type__versions_get`
List context versions for the selected business and context type.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `context_type` | path | "business" | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_BusinessContextVersionSummaryData__
- `request_id` · string · required
- `success` · true
- `data` · BusinessContextVersionSummaryData[] · required
- `id` · string · required
- `business_id` · string · required
- `context_type` · "business" · required
- `version_number` · integer · required
- `source_kind` · "manual" | "generated" | "restored" · required
- `source_industry_id` · string | null
- `restored_from_version_id` · string | null
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/contexts/{context_type}/versions/{version_id}
Get business context version detail
Operation id: `get_business_context_version_api_v1_admin_businesses__business_id__contexts__context_type__versions__version_id__get`
Get the saved content for a historical version.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `context_type` | path | "business" | yes | |
| `version_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessContextVersionData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessContextVersionData · required: Full historical context version payload.
- `id` · string · required
- `business_id` · string · required
- `context_type` · "business" · required
- `version_number` · integer · required
- `source_kind` · "manual" | "generated" | "restored" · required
- `source_industry_id` · string | null
- `restored_from_version_id` · string | null
- `created_at` · integer · required
- `content` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contexts/{context_type}/versions/{version_id}/restore
Restore business context version
Operation id: `restore_business_context_version_api_v1_admin_businesses__business_id__contexts__context_type__versions__version_id__restore_post`
Restore a historical version by copying it into a new current version.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `context_type` | path | "business" | yes | |
| `version_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessContextCurrentData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessContextCurrentData · required: Current context payload for a business + context type.
- `id` · string · required
- `business_id` · string · required
- `context_type` · "business" · required
- `version_number` · integer · required
- `content` · string · required
- `source_kind` · "manual" | "generated" | "restored" · required
- `source_industry_id` · string | null
- `restored_from_version_id` · string | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/contexts/business/generate-outline
Generate business context outline
Operation id: `generate_business_context_outline_api_v1_admin_businesses__business_id__contexts_business_generate_outline_post`
Generate a Business Context draft from current live SOR business data.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessContextGenerateOutlineData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessContextGenerateOutlineData · required: Draft generated for the Business Context page.
- `context_type` · "business"
- `content` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Conversations
15 endpoints. HTML: https://developers.keystone.app/api/console/conversations/
### GET /api/v1/admin/conversations
List conversations
Operation id: `list_conversations_api_v1_admin_conversations_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | query | string (uuid) \| null | no | Scope contact hydration to this business. When provided, each list item's `contact` field is the business_contact record for that business; without it, contact is picked from any business the consumer is known to (first match). |
| `status` | query | string \| null | no | OPEN \| CLOSED \| SNOOZED |
| `assigned_to` | query | integer \| null | no | |
| `unread_only` | query | boolean | no | |
| `needs_attention` | query | boolean \| null | no | Filter to conversations whose business contact is flagged needs-attention. Requires `business_id` — contacts are per-business while consumers are global, so an unscoped filter would match contacts flagged in other businesses. |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ConversationListItem__
- `request_id` · string · required
- `success` · true
- `data` · ConversationListItem[] · required
- `id` · string · required
- `consumer_id` · string | null
- `status` · string · required
- `assigned_to` · integer | null
- `ai_enabled` · boolean
- `ai_mode` · string
- `last_message_at` · string (date-time) | null
- `last_message_preview` · string | null
- `last_message_channel` · string | null
- `unread_count` · integer
- `message_count` · integer
- `snoozed_until` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `reply_channel` · ReplyChannelData | null
- `contact` · ConversationContactSummary | null
- `businesses` · ConversationBusinessSummary[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/conversations
Create or fetch conversation for a contact (idempotent)
Operation id: `create_conversation_api_v1_admin_conversations_post`
Start a conversation with a contact from the console. Returns 200 with the existing conversation when one already exists for the contact's consumer (idempotent). Passing `initial_message` sends the first outbound atomically.
Console-created conversations default to `ai_enabled=True`, `ai_mode='AUTONOMOUS'` — the bot drives the thread out of the box. Operators opt out via PATCH /conversations/{id} or by flipping the contact-level `auto_contact_enabled` toggle (which cascades here).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ConversationCreateBody
- `business_id` · string · required
- `contact_id` · string · required
- `channel` · string | null: IMESSAGE (only supported channel today)
- `initial_message` · MessageSendBody | null
- `body` · string · required
- `attachments` · MessageAttachment[]
- `url` · string · required
- `content_type` · string | null
- `filename` · string | null
- `source_url` · string | null
- `gcs` · object | null
- `ingest_status` · string | null
- `content_type` · string: TEXT \| MEDIA
- `idempotency_key` · string | null
- `channel` · string | null
- `idempotency_key` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ConversationCreateResult_
- `request_id` · string · required
- `success` · true
- `data` · ConversationCreateResult · required: POST /conversations response data. `created` is True on first insert, False when the existing conversation for this consumer was returned idempotently. Frontend uses it to decide whether to trigger an onboarding UI vs. an append.
- `conversation` · ConversationData · required: Conversation detail / list item response shape.
- `message` · MessageData | null
- `created` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/conversations/{conversation_id}
Get conversation
Operation id: `get_conversation_api_v1_admin_conversations__conversation_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ConversationData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationData · required: Conversation detail / list item response shape.
- `id` · string · required
- `consumer_id` · string | null
- `status` · string · required
- `assigned_to` · integer | null
- `ai_enabled` · boolean
- `ai_mode` · string
- `last_message_at` · string (date-time) | null
- `last_message_preview` · string | null
- `last_message_channel` · string | null
- `unread_count` · integer
- `message_count` · integer
- `snoozed_until` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `reply_channel` · ReplyChannelData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/conversations/{conversation_id}
Update conversation
Operation id: `update_conversation_api_v1_admin_conversations__conversation_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ConversationUpdateBody
- `status` · string | null: OPEN \| CLOSED \| SNOOZED
- `assigned_to` · integer | null
- `ai_enabled` · boolean | null
- `ai_mode` · string | null: AUTONOMOUS \| ASSIST \| OFF
- `snoozed_until` · string (date-time) | null
**Responses**
- `200` Successful Response: SuccessResponse_ConversationData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationData · required: Conversation detail / list item response shape.
- `id` · string · required
- `consumer_id` · string | null
- `status` · string · required
- `assigned_to` · integer | null
- `ai_enabled` · boolean
- `ai_mode` · string
- `last_message_at` · string (date-time) | null
- `last_message_preview` · string | null
- `last_message_channel` · string | null
- `unread_count` · integer
- `message_count` · integer
- `snoozed_until` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `reply_channel` · ReplyChannelData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/conversations/{conversation_id}/messages
List messages in a conversation
Operation id: `list_messages_api_v1_admin_conversations__conversation_id__messages_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_MessageData__
- `request_id` · string · required
- `success` · true
- `data` · MessageData[] · required
- `id` · string · required
- `conversation_id` · string · required
- `consumer_id` · string · required
- `direction` · string · required
- `sender_type` · string · required
- `sender_id` · string | null
- `sender_display_name` · string | null
- `channel` · string · required
- `content_type` · string · required
- `body` · string | null
- `attachments` · (FormSubmissionAttachment | MessageAttachment)[]
- `external_id` · string | null
- `status` · string · required
- `error_code` · string | null
- `error_message` · string | null
- `ai_metadata` · object
- `created_at` · string (date-time) · required
- `delivered_at` · string (date-time) | null
- `read_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/conversations/{conversation_id}/messages
Send outbound message
Operation id: `send_message_api_v1_admin_conversations__conversation_id__messages_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): MessageSendBody
- `body` · string · required
- `attachments` · MessageAttachment[]
- `url` · string · required
- `content_type` · string | null
- `filename` · string | null
- `source_url` · string | null
- `gcs` · object | null
- `ingest_status` · string | null
- `content_type` · string: TEXT \| MEDIA
- `idempotency_key` · string | null
- `channel` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_MessageData_
- `request_id` · string · required
- `success` · true
- `data` · MessageData · required: Message detail / list item response shape.
- `id` · string · required
- `conversation_id` · string · required
- `consumer_id` · string · required
- `direction` · string · required
- `sender_type` · string · required
- `sender_id` · string | null
- `sender_display_name` · string | null
- `channel` · string · required
- `content_type` · string · required
- `body` · string | null
- `attachments` · (FormSubmissionAttachment | MessageAttachment)[]
- `external_id` · string | null
- `status` · string · required
- `error_code` · string | null
- `error_message` · string | null
- `ai_metadata` · object
- `created_at` · string (date-time) · required
- `delivered_at` · string (date-time) | null
- `read_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/conversations/{conversation_id}/messages/{message_id}
Delete a DRAFT message (ASSIST-mode reject)
Operation id: `delete_message_api_v1_admin_conversations__conversation_id__messages__message_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `message_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/conversations/{conversation_id}/messages/{message_id}/retry
Retry a FAILED or PENDING outbound message
Operation id: `retry_message_api_v1_admin_conversations__conversation_id__messages__message_id__retry_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `message_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_MessageData_
- `request_id` · string · required
- `success` · true
- `data` · MessageData · required: Message detail / list item response shape.
- `id` · string · required
- `conversation_id` · string · required
- `consumer_id` · string · required
- `direction` · string · required
- `sender_type` · string · required
- `sender_id` · string | null
- `sender_display_name` · string | null
- `channel` · string · required
- `content_type` · string · required
- `body` · string | null
- `attachments` · (FormSubmissionAttachment | MessageAttachment)[]
- `external_id` · string | null
- `status` · string · required
- `error_code` · string | null
- `error_message` · string | null
- `ai_metadata` · object
- `created_at` · string (date-time) · required
- `delivered_at` · string (date-time) | null
- `read_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/conversations/{conversation_id}/messages/{message_id}/send
Approve and send a DRAFT (e.g. AI-authored) message
Operation id: `send_draft_api_v1_admin_conversations__conversation_id__messages__message_id__send_post`
Used by the ASSIST-mode approve flow: promotes a DRAFT outbound row to PENDING, stamps ai_metadata.review_status='approved' with the approver's user_id, and publishes to raven. Optional `edited_body` in the body allows inline edits before approval.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `message_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): DraftSendBody | null
- `edited_body` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_MessageData_
- `request_id` · string · required
- `success` · true
- `data` · MessageData · required: Message detail / list item response shape.
- `id` · string · required
- `conversation_id` · string · required
- `consumer_id` · string · required
- `direction` · string · required
- `sender_type` · string · required
- `sender_id` · string | null
- `sender_display_name` · string | null
- `channel` · string · required
- `content_type` · string · required
- `body` · string | null
- `attachments` · (FormSubmissionAttachment | MessageAttachment)[]
- `external_id` · string | null
- `status` · string · required
- `error_code` · string | null
- `error_message` · string | null
- `ai_metadata` · object
- `created_at` · string (date-time) · required
- `delivered_at` · string (date-time) | null
- `read_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/conversations/{conversation_id}/read
Mark conversation as read (zero unread_count)
Operation id: `mark_conversation_read_api_v1_admin_conversations__conversation_id__read_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `conversation_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ConversationData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationData · required: Conversation detail / list item response shape.
- `id` · string · required
- `consumer_id` · string | null
- `status` · string · required
- `assigned_to` · integer | null
- `ai_enabled` · boolean
- `ai_mode` · string
- `last_message_at` · string (date-time) | null
- `last_message_preview` · string | null
- `last_message_channel` · string | null
- `unread_count` · integer
- `message_count` · integer
- `snoozed_until` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `reply_channel` · ReplyChannelData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/conversations/configs
List all conversations config versions (newest first)
Operation id: `list_conversations_configs_api_v1_admin_conversations_configs_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ConversationsConfigData__
- `request_id` · string · required
- `success` · true
- `data` · ConversationsConfigData[] · required
- `id` · string · required
- `version` · integer · required
- `is_active` · boolean · required
- `persona_system_prompt` · string · required
- `prompt_version` · string · required
- `classification_provider` · string · required
- `conversational_provider` · string · required
- `summarization_provider` · string · required
- `classification_enabled` · boolean · required
- `conversational_enabled` · boolean · required
- `conversational_multimodal_enabled` · boolean
- `min_autonomous_confidence` · number · required
- `forbidden_topics` · string[]
- `escalation_keywords` · string[]
- `max_auto_replies_per_hour_per_consumer` · integer · required
- `max_auto_replies_per_day_global` · integer · required
- `summary_trigger_message_count` · integer · required
- `verbatim_tail_size` · integer · required
- `summary_max_sentences` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `created_by` · string (uuid) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/conversations/configs
Create a new conversations config version
Operation id: `create_conversations_config_version_api_v1_admin_conversations_configs_post`
Inserts a new version that supersedes the current active row. Any field omitted in the body is inherited from the current active version, so callers can submit a small diff.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ConversationsConfigCreateBody
- `persona_system_prompt` · string | null
- `prompt_version` · string | null
- `classification_provider` · string | null
- `conversational_provider` · string | null
- `summarization_provider` · string | null
- `classification_enabled` · boolean | null
- `conversational_enabled` · boolean | null
- `conversational_multimodal_enabled` · boolean | null
- `min_autonomous_confidence` · number | null
- `forbidden_topics` · string[] | null
- `escalation_keywords` · string[] | null
- `max_auto_replies_per_hour_per_consumer` · integer | null
- `max_auto_replies_per_day_global` · integer | null
- `summary_trigger_message_count` · integer | null
- `verbatim_tail_size` · integer | null
- `summary_max_sentences` · integer | null
**Responses**
- `201` Successful Response: SuccessResponse_ConversationsConfigData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationsConfigData · required: Response shape for /admin/conversations/configs and friends.
- `id` · string · required
- `version` · integer · required
- `is_active` · boolean · required
- `persona_system_prompt` · string · required
- `prompt_version` · string · required
- `classification_provider` · string · required
- `conversational_provider` · string · required
- `summarization_provider` · string · required
- `classification_enabled` · boolean · required
- `conversational_enabled` · boolean · required
- `conversational_multimodal_enabled` · boolean
- `min_autonomous_confidence` · number · required
- `forbidden_topics` · string[]
- `escalation_keywords` · string[]
- `max_auto_replies_per_hour_per_consumer` · integer · required
- `max_auto_replies_per_day_global` · integer · required
- `summary_trigger_message_count` · integer · required
- `verbatim_tail_size` · integer · required
- `summary_max_sentences` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `created_by` · string (uuid) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/conversations/configs/{version}
Get a conversations config by version
Operation id: `get_conversations_config_by_version_api_v1_admin_conversations_configs__version__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `version` | path | integer | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ConversationsConfigData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationsConfigData · required: Response shape for /admin/conversations/configs and friends.
- `id` · string · required
- `version` · integer · required
- `is_active` · boolean · required
- `persona_system_prompt` · string · required
- `prompt_version` · string · required
- `classification_provider` · string · required
- `conversational_provider` · string · required
- `summarization_provider` · string · required
- `classification_enabled` · boolean · required
- `conversational_enabled` · boolean · required
- `conversational_multimodal_enabled` · boolean
- `min_autonomous_confidence` · number · required
- `forbidden_topics` · string[]
- `escalation_keywords` · string[]
- `max_auto_replies_per_hour_per_consumer` · integer · required
- `max_auto_replies_per_day_global` · integer · required
- `summary_trigger_message_count` · integer · required
- `verbatim_tail_size` · integer · required
- `summary_max_sentences` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `created_by` · string (uuid) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/conversations/configs/active
Get the active conversations config
Operation id: `get_active_conversations_config_api_v1_admin_conversations_configs_active_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ConversationsConfigData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationsConfigData · required: Response shape for /admin/conversations/configs and friends.
- `id` · string · required
- `version` · integer · required
- `is_active` · boolean · required
- `persona_system_prompt` · string · required
- `prompt_version` · string · required
- `classification_provider` · string · required
- `conversational_provider` · string · required
- `summarization_provider` · string · required
- `classification_enabled` · boolean · required
- `conversational_enabled` · boolean · required
- `conversational_multimodal_enabled` · boolean
- `min_autonomous_confidence` · number · required
- `forbidden_topics` · string[]
- `escalation_keywords` · string[]
- `max_auto_replies_per_hour_per_consumer` · integer · required
- `max_auto_replies_per_day_global` · integer · required
- `summary_trigger_message_count` · integer · required
- `verbatim_tail_size` · integer · required
- `summary_max_sentences` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `created_by` · string (uuid) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/global-conversations
List conversations across all businesses (admin)
Operation id: `list_global_conversations_api_v1_admin_global_conversations_get`
Admin cross-business inbox. Always unscoped. `needs_attention` matches conversations where any linked business_contact is flagged. Each row includes `businesses` (id + name) for UI badges and `contact.business_id` for actions.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `status` | query | string \| null | no | OPEN \| CLOSED \| SNOOZED |
| `assigned_to` | query | integer \| null | no | |
| `unread_only` | query | boolean | no | |
| `needs_attention` | query | boolean \| null | no | Filter to conversations where any linked business_contact is flagged needs-attention (cross-business). |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ConversationListItem__
- `request_id` · string · required
- `success` · true
- `data` · ConversationListItem[] · required
- `id` · string · required
- `consumer_id` · string | null
- `status` · string · required
- `assigned_to` · integer | null
- `ai_enabled` · boolean
- `ai_mode` · string
- `last_message_at` · string (date-time) | null
- `last_message_preview` · string | null
- `last_message_channel` · string | null
- `unread_count` · integer
- `message_count` · integer
- `snoozed_until` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `reply_channel` · ReplyChannelData | null
- `contact` · ConversationContactSummary | null
- `businesses` · ConversationBusinessSummary[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Conversations agent
2 endpoints. HTML: https://developers.keystone.app/api/console/conversations-agent/
### GET /api/v1/businesses/{business_id}/conversations-agent/config
Get effective conversations-agent settings for a business
Operation id: `get_config_api_v1_businesses__business_id__conversations_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ConversationsAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationsAgentConfigData · required: Effective conversations-agent settings for a business.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `website_lead_follow_up_enabled` · boolean
- `fb_native_form_follow_up_enabled` · boolean
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/conversations-agent/config
Update conversations-agent settings for a business
Operation id: `patch_config_api_v1_businesses__business_id__conversations_agent_config_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ConversationsAgentConfigPatchBody
- `enabled` · boolean | null
- `instructions` · string | null
- `website_lead_follow_up_enabled` · boolean | null
- `fb_native_form_follow_up_enabled` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_ConversationsAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · ConversationsAgentConfigData · required: Effective conversations-agent settings for a business.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `website_lead_follow_up_enabled` · boolean
- `fb_native_form_follow_up_enabled` · boolean
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Current user
1 endpoints. HTML: https://developers.keystone.app/api/console/current-user/
### GET /api/v1/admin/current_user
Current authenticated user profile
Operation id: `get_current_user_api_v1_admin_current_user_get`
Returns the JWT's `user_id` and `role_id` augmented with the display name + email fetched from Heimdal `/users/current_user`. Side effect: refreshes the Redis Heimdal-user cache so message list endpoints can render this user's name on outbound bubbles without a cross-service round-trip.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CurrentUserData_
- `request_id` · string · required
- `success` · true
- `data` · CurrentUserData · required: Profile of the currently authenticated keystone user. Sourced from Heimdal `/users/current_user`; sor-service caches the relevant bits in Redis (see `services/keystone_user_cache.py`) so the message list can resolve `sender_display_name` without a cross-service round-trip.
- `user_id` · string · required
- `role_id` · integer · required
- `first_name` · string | null
- `last_name` · string | null
- `email` · string | null
- `display_name` · string | null
- `features` · CurrentUserFeatures · required: Runtime product access resolved by SOR for the authenticated user.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: FAQ
6 endpoints. HTML: https://developers.keystone.app/api/console/faq/
### GET /api/v1/admin/businesses/{business_id}/faqs
Get all FAQs
Operation id: `list_faqs_api_v1_admin_businesses__business_id__faqs_get`
Get all FAQs for a business (ordered by display_order).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_FaqData__
- `request_id` · string · required
- `success` · true
- `data` · FaqData[] · required
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/faqs
Create FAQ
Operation id: `create_faq_api_v1_admin_businesses__business_id__faqs_post`
Create a new FAQ.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): FaqCreateBody
- `question` · string · required
- `answer` · string · required
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_FaqData_
- `request_id` · string · required
- `success` · true
- `data` · FaqData · required: FAQ for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/faqs/{faq_id}
Get FAQ details
Operation id: `get_faq_api_v1_admin_businesses__business_id__faqs__faq_id__get`
Get FAQ by id. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `faq_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FaqData_
- `request_id` · string · required
- `success` · true
- `data` · FaqData · required: FAQ for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/faqs/{faq_id}
Update FAQ
Operation id: `update_faq_api_v1_admin_businesses__business_id__faqs__faq_id__put`
Update FAQ details. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `faq_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): FaqUpdateBody
- `question` · string | null
- `answer` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_FaqData_
- `request_id` · string · required
- `success` · true
- `data` · FaqData · required: FAQ for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/faqs/{faq_id}
Delete FAQ
Operation id: `delete_faq_api_v1_admin_businesses__business_id__faqs__faq_id__delete`
Delete a FAQ. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `faq_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/faqs/reorder
Reorder FAQs
Operation id: `reorder_faqs_api_v1_admin_businesses__business_id__faqs_reorder_put`
Reorder FAQs by ordered list of FAQ ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): FaqReorderBody
- `ordered_faq_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_FaqData__
- `request_id` · string · required
- `success` · true
- `data` · FaqData[] · required
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Forms
8 endpoints. HTML: https://developers.keystone.app/api/console/forms/
### GET /api/v1/admin/businesses/{business_id}/form_submissions
List all form submissions
Operation id: `list_submissions_api_v1_admin_businesses__business_id__form_submissions_get`
List all form submissions; cursor is the last ``id`` from the previous page (UUID v7).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `form_type` | query | string \| null | no | Filter by form identity (built-in or custom slug, e.g. lead, demo-request) |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_FormSubmissionSummary__
- `request_id` · string · required
- `success` · true
- `data` · FormSubmissionSummary[] · required
- `id` · string · required
- `form_type` · string · required
- `data` · FormSubmissionDataSchema: Submitted form data - arbitrary key/value pairs matching the form's fields definition. Common fields may include: - Lead forms: firstName, lastName, email, phone, message - Consent: transactional_sms_consent, marketing_sms_consent, tos_privacy_consent - Job applications: coverLetter, jobSlug, jobId - Any custom fields defined in the form's fields array
- `form` · FormSummary | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/form_submissions/{submission_id}
Get form submission details
Operation id: `get_submission_api_v1_admin_businesses__business_id__form_submissions__submission_id__get`
Get detailed form submission including form info.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `submission_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FormSubmissionDetail_
- `request_id` · string · required
- `success` · true
- `data` · FormSubmissionDetail · required: Full form submission data.
- `id` · string · required
- `business_id` · string · required
- `form_id` · string · required
- `form_type` · string · required
- `data` · FormSubmissionDataSchema: Submitted form data - arbitrary key/value pairs matching the form's fields definition. Common fields may include: - Lead forms: firstName, lastName, email, phone, message - Consent: transactional_sms_consent, marketing_sms_consent, tos_privacy_consent - Job applications: coverLetter, jobSlug, jobId - Any custom fields defined in the form's fields array
- `form` · FormSummary | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/form_submissions/{submission_id}
Delete form submission
Operation id: `delete_submission_api_v1_admin_businesses__business_id__form_submissions__submission_id__delete`
Soft delete a form submission. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `submission_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/forms
List forms
Operation id: `list_forms_api_v1_admin_businesses__business_id__forms_get`
List forms for a business (optionally filtered by status) with submission counts.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | Filter by status: draft, live, archived |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_FormSummary__
- `request_id` · string · required
- `success` · true
- `data` · FormSummary[] · required
- `id` · string · required
- `business_id` · string · required
- `name` · string · required
- `form_type` · string · required
- `status` · FormStatus: Lifecycle status of a form. Only ``live`` forms are served publicly.
- `is_builtin` · boolean
- `field_count` · integer
- `submissions_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/forms
Create a custom form
Operation id: `create_form_api_v1_admin_businesses__business_id__forms_post`
Create a custom form for a business.
``form_type`` is the immutable per-business identity; when omitted it is
slugified from ``name``. A reserved built-in identity (``lead``, …) is rejected
(400 RESERVED_FORM_TYPE); a duplicate identity → 409 FORM_ALREADY_EXISTS. New
forms default to ``draft``. Snapshots are lazy: a draft gets no snapshot; the
first snapshot is created when the form is created (or later set) ``live``.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): FormCreateRequest
- `name` · string · required
- `form_type` · string | null
- `fields` · (FormFieldSchema | FormFieldSchema[])[]
- `settings` · FormSettingsSchema | null
- `send_events` · string[]
- `show_transactional_sms_consent` · boolean
- `show_marketing_sms_consent` · boolean
- `show_tos_privacy_consent` · boolean
- `tos_privacy_required` · boolean
- `sync_contact` · boolean
- `sync_job` · boolean
- `enroll_conversation_ai` · boolean
- `allow_extra_fields` · boolean
- `status` · FormStatus: Lifecycle status of a form. Only ``live`` forms are served publicly.
- `description` · string | null
- `submit_button_label` · string | null
- `confirmation_message` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_FormData_
- `request_id` · string · required
- `success` · true
- `data` · FormData · required: Full form data including fields and settings.
- `id` · string · required
- `business_id` · string · required
- `name` · string · required
- `form_type` · string · required
- `status` · FormStatus: Lifecycle status of a form. Only ``live`` forms are served publicly.
- `is_builtin` · boolean
- `fields` · (FormFieldSchema | FormFieldSchema[])[]
- `settings` · FormSettingsSchema: Settings for form behavior and consent flags.
- `description` · string | null
- `submit_button_label` · string | null
- `confirmation_message` · string | null
- `submissions_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/forms/{form_id}
Get form details
Operation id: `get_form_api_v1_admin_businesses__business_id__forms__form_id__get`
Get detailed form including fields, settings, and submission count.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `form_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FormData_
- `request_id` · string · required
- `success` · true
- `data` · FormData · required: Full form data including fields and settings.
- `id` · string · required
- `business_id` · string · required
- `name` · string · required
- `form_type` · string · required
- `status` · FormStatus: Lifecycle status of a form. Only ``live`` forms are served publicly.
- `is_builtin` · boolean
- `fields` · (FormFieldSchema | FormFieldSchema[])[]
- `settings` · FormSettingsSchema: Settings for form behavior and consent flags.
- `description` · string | null
- `submit_button_label` · string | null
- `confirmation_message` · string | null
- `submissions_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/forms/{form_id}
Update form
Operation id: `update_form_api_v1_admin_businesses__business_id__forms__form_id__put`
Update a form's name, fields, and/or settings.
Partial update: omitted keys are left unchanged. ``form_type`` is immutable;
fields marked ``fixed: true`` cannot be removed, modified, or unmarked unless
``bypass_fixed: true`` is sent — which requires the caller to be a **platform
admin** (role_id == 100). Changing a **custom** form's ``settings`` likewise
requires platform admin (a no-op settings payload is allowed). 404 if the form
does not exist.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `form_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): FormUpdateRequest
- `name` · string | null
- `fields` · (FormFieldSchema | FormFieldSchema[])[] | null
- `settings` · FormSettingsSchema | null
- `send_events` · string[]
- `show_transactional_sms_consent` · boolean
- `show_marketing_sms_consent` · boolean
- `show_tos_privacy_consent` · boolean
- `tos_privacy_required` · boolean
- `sync_contact` · boolean
- `sync_job` · boolean
- `enroll_conversation_ai` · boolean
- `allow_extra_fields` · boolean
- `status` · FormStatus | null
- `description` · string | null
- `submit_button_label` · string | null
- `confirmation_message` · string | null
- `bypass_fixed` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_FormData_
- `request_id` · string · required
- `success` · true
- `data` · FormData · required: Full form data including fields and settings.
- `id` · string · required
- `business_id` · string · required
- `name` · string · required
- `form_type` · string · required
- `status` · FormStatus: Lifecycle status of a form. Only ``live`` forms are served publicly.
- `is_builtin` · boolean
- `fields` · (FormFieldSchema | FormFieldSchema[])[]
- `settings` · FormSettingsSchema: Settings for form behavior and consent flags.
- `description` · string | null
- `submit_button_label` · string | null
- `confirmation_message` · string | null
- `submissions_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/forms/{form_id}/submissions
List submissions for a form
Operation id: `list_form_submissions_api_v1_admin_businesses__business_id__forms__form_id__submissions_get`
List submissions for a specific form; cursor is the last ``id`` from the previous page (UUID v7).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `form_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_FormSubmissionSummary__
- `request_id` · string · required
- `success` · true
- `data` · FormSubmissionSummary[] · required
- `id` · string · required
- `form_type` · string · required
- `data` · FormSubmissionDataSchema: Submitted form data - arbitrary key/value pairs matching the form's fields definition. Common fields may include: - Lead forms: firstName, lastName, email, phone, message - Consent: transactional_sms_consent, marketing_sms_consent, tos_privacy_consent - Job applications: coverLetter, jobSlug, jobId - Any custom fields defined in the form's fields array
- `form` · FormSummary | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Front desk
3 endpoints. HTML: https://developers.keystone.app/api/console/front-desk/
### GET /api/v1/admin/businesses/{business_id}/front-desk
Front Desk status for a business
Operation id: `get_front_desk_api_v1_admin_businesses__business_id__front_desk_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FrontDeskStatusData_
- `request_id` · string · required
- `success` · true
- `data` · FrontDeskStatusData · required: The Front Desk state for one business, as the settings page shows it. ``enabled`` is derived: true iff the business holds a live Front Desk number (design doc §6.3 — no separate config row in phase 1). ``primary_phone`` is the business's own published number, shown so the owner can set up carrier forwarding from it to ``phone_number``.
- `business_id` · string · required
- `enabled` · boolean · required
- `phone_number` · string | null
- `number_status` · string | null
- `primary_phone` · string | null
- `can_enable` · boolean · required
- `blocked_reason` · "no_primary_phone" | "not_configured" | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/front-desk/disable
Turn Front Desk off (releases the phone number right away)
Operation id: `disable_front_desk_api_v1_admin_businesses__business_id__front_desk_disable_post`
Releases the number at Retell and marks it released here — the number is gone for good; turning Front Desk on again buys a fresh one. Idempotent when already off.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FrontDeskStatusData_
- `request_id` · string · required
- `success` · true
- `data` · FrontDeskStatusData · required: The Front Desk state for one business, as the settings page shows it. ``enabled`` is derived: true iff the business holds a live Front Desk number (design doc §6.3 — no separate config row in phase 1). ``primary_phone`` is the business's own published number, shown so the owner can set up carrier forwarding from it to ``phone_number``.
- `business_id` · string · required
- `enabled` · boolean · required
- `phone_number` · string | null
- `number_status` · string | null
- `primary_phone` · string | null
- `can_enable` · boolean · required
- `blocked_reason` · "no_primary_phone" | "not_configured" | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/front-desk/enable
Turn Front Desk on (auto-provisions a phone number)
Operation id: `enable_front_desk_api_v1_admin_businesses__business_id__front_desk_enable_post`
Buys a US number in the area code of the business's primary phone (same-state, then any-US fallback), binds the shared Front Desk agent, and activates it immediately. Idempotent when already on. 422 when the business has no primary phone; 503 when Retell is not configured.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FrontDeskStatusData_
- `request_id` · string · required
- `success` · true
- `data` · FrontDeskStatusData · required: The Front Desk state for one business, as the settings page shows it. ``enabled`` is derived: true iff the business holds a live Front Desk number (design doc §6.3 — no separate config row in phase 1). ``primary_phone`` is the business's own published number, shown so the owner can set up carrier forwarding from it to ``phone_number``.
- `business_id` · string · required
- `enabled` · boolean · required
- `phone_number` · string | null
- `number_status` · string | null
- `primary_phone` · string | null
- `can_enable` · boolean · required
- `blocked_reason` · "no_primary_phone" | "not_configured" | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Global agent
2 endpoints. HTML: https://developers.keystone.app/api/console/global-agent/
### GET /api/v1/businesses/{business_id}/global-agent/config
Get effective global agent settings for a business
Operation id: `get_config_api_v1_businesses__business_id__global_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_GlobalAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · GlobalAgentConfigData · required: Business-wide instructions shared across all agents.
- `business_id` · string · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/global-agent/config
Update global agent settings for a business
Operation id: `patch_config_api_v1_businesses__business_id__global_agent_config_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): GlobalAgentConfigPatchBody
- `instructions` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_GlobalAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · GlobalAgentConfigData · required: Business-wide instructions shared across all agents.
- `business_id` · string · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Import data
19 endpoints. HTML: https://developers.keystone.app/api/console/import-data/
### GET /api/v1/admin/businesses/{business_id}/import-data/custom-imports
List custom import runs
Operation id: `list_custom_import_runs_api_v1_admin_businesses__business_id__import_data_custom_imports_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_CustomImportRunData__
- `request_id` · string · required
- `success` · true
- `data` · CustomImportRunData[] · required
- `id` · string · required
- `business_id` · string · required
- `import_name` · string | null
- `status` · "queued" | "running" | "awaiting_review" | "completed" | "failed" · required
- `merge_mode` · string · required
- `text_content` · string | null
- `ingest` · ImportDataStageStatus | null
- `extract` · ImportDataStageStatus | null
- `merge` · ImportDataStageStatus | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/import-data/custom-imports
Create a custom import run
Operation id: `create_custom_import_api_v1_admin_businesses__business_id__import_data_custom_imports_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (multipart/form-data, required): Body_create_custom_import_api_v1_admin_businesses__business_id__import_data_custom_imports_post
- `import_name` · string | null
- `text_content` · string | null
- `files` · string[] | null
**Responses**
- `201` Successful Response: SuccessResponse_CustomImportActionResponseData_
- `request_id` · string · required
- `success` · true
- `data` · CustomImportActionResponseData · required
- `run` · CustomImportRunData · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/custom-imports/{import_run_id}
Get a custom import run
Operation id: `get_custom_import_run_api_v1_admin_businesses__business_id__import_data_custom_imports__import_run_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `import_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CustomImportRunData_
- `request_id` · string · required
- `success` · true
- `data` · CustomImportRunData · required
- `id` · string · required
- `business_id` · string · required
- `import_name` · string | null
- `status` · "queued" | "running" | "awaiting_review" | "completed" | "failed" · required
- `merge_mode` · string · required
- `text_content` · string | null
- `ingest` · ImportDataStageStatus | null
- `extract` · ImportDataStageStatus | null
- `merge` · ImportDataStageStatus | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/import-data/custom-imports/{import_run_id}/extract
Re-run extraction on stored custom import content
Operation id: `rerun_custom_import_extraction_api_v1_admin_businesses__business_id__import_data_custom_imports__import_run_id__extract_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `import_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ReExtractResponseData_
- `request_id` · string · required
- `success` · true
- `data` · ReExtractResponseData · required
- `run_id` · string · required
- `status` · string · required
- `attempt_id` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/custom-imports/{import_run_id}/extraction
Get the latest custom import extraction payload
Operation id: `get_custom_import_extraction_api_v1_admin_businesses__business_id__import_data_custom_imports__import_run_id__extraction_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `import_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CustomImportExtractionData_
- `request_id` · string · required
- `success` · true
- `data` · CustomImportExtractionData · required
- `attempt_id` · string · required
- `run_id` · string · required
- `status` · "queued" | "running" | "completed" | "failed" · required
- `extractor_version` · string · required
- `started_at` · integer | null
- `completed_at` · integer | null
- `payload` · ExtractionPayloadData
- `summary` · object | null
- `error_message` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/custom-imports/{import_run_id}/files
List files attached to a custom import run
Operation id: `list_custom_import_files_api_v1_admin_businesses__business_id__import_data_custom_imports__import_run_id__files_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `import_run_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_CustomImportFileData__
- `request_id` · string · required
- `success` · true
- `data` · CustomImportFileData[] · required
- `id` · string · required
- `original_filename` · string · required
- `mime_type` · string | null
- `byte_size` · integer | null
- `checksum_sha256` · string | null
- `storage_bucket` · string | null
- `storage_blob_name` · string | null
- `storage_object_url` · string | null
- `extracted_text` · string | null
- `normalized_text` · string | null
- `parse_status` · string · required
- `parse_error` · string | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/custom-imports/{import_run_id}/files/{file_id}/download
Download a stored custom import file
Operation id: `download_custom_import_file_api_v1_admin_businesses__business_id__import_data_custom_imports__import_run_id__files__file_id__download_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `import_run_id` | path | string (uuid) | yes | |
| `file_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/import-data/custom-imports/{import_run_id}/merge
Apply a selected subset of a custom import into the business record
Operation id: `apply_custom_import_merge_api_v1_admin_businesses__business_id__import_data_custom_imports__import_run_id__merge_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `import_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): MergeSelectionBody
- `extract_attempt_id` · string · required
- `business_profile` · MergeBusinessProfileSelection | null
- `scalars` · string[]
- `contact_info` · string[]
- `address` · boolean
- `descriptions` · string[]
- `social_profiles` · string[]
- `services` · integer[]
- `packages` · integer[]
- `service_items` · integer[]
- `offers` · integer[]
- `jobs` · integer[]
- `faqs` · integer[]
- `locations` · integer[]
- `team_members` · integer[]
- `blog_posts` · integer[]
- `blog_post_authors` · integer[]
- `blog_post_tags` · integer[]
- `contexts` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_MergeApplyResponseData_
- `request_id` · string · required
- `success` · true
- `data` · MergeApplyResponseData · required
- `run_id` · string · required
- `status` · string · required
- `merge_summary` · MergeSummaryData · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/custom-imports/{import_run_id}/review
Get the merged review view for a custom import run
Operation id: `get_custom_import_review_api_v1_admin_businesses__business_id__import_data_custom_imports__import_run_id__review_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `import_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CustomImportReviewData_
- `request_id` · string · required
- `success` · true
- `data` · CustomImportReviewData · required
- `run` · CustomImportRunData · required
- `extraction` · CustomImportExtractionData | null
- `merge_summary` · MergeSummaryData | null
- `current_business_profile` · ExtractedBusinessProfileData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/website-scrapes
List website scrape runs
Operation id: `list_website_scrape_runs_api_v1_admin_businesses__business_id__import_data_website_scrapes_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_WebsiteScrapeRunData__
- `request_id` · string · required
- `success` · true
- `data` · WebsiteScrapeRunData[] · required
- `id` · string · required
- `business_id` · string · required
- `submitted_url` · string · required
- `normalized_domain` · string · required
- `status` · "queued" | "running" | "awaiting_review" | "completed" | "failed" · required
- `scope_mode` · string · required
- `merge_mode` · string · required
- `onboarding` · object | null
- `rescrape_of` · string | null
- `last_heartbeat_at` · integer | null
- `crawl` · ImportDataStageStatus | null
- `extract` · ImportDataStageStatus | null
- `merge` · ImportDataStageStatus | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/import-data/website-scrapes
Start a website scrape run
Operation id: `start_website_scrape_api_v1_admin_businesses__business_id__import_data_website_scrapes_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteScrapeStartBody
- `website_url` · string · required
- `idempotency_key` · string | null
- `rescrape_of` · string | null
- `auto_onboard` · boolean
- `user_prompt` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_RunActionResponseData_
- `request_id` · string · required
- `success` · true
- `data` · RunActionResponseData · required
- `run` · WebsiteScrapeRunData · required
- `reused_existing_run` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}
Get a website scrape run
Operation id: `get_website_scrape_run_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteScrapeRunData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteScrapeRunData · required
- `id` · string · required
- `business_id` · string · required
- `submitted_url` · string · required
- `normalized_domain` · string · required
- `status` · "queued" | "running" | "awaiting_review" | "completed" | "failed" · required
- `scope_mode` · string · required
- `merge_mode` · string · required
- `onboarding` · object | null
- `rescrape_of` · string | null
- `last_heartbeat_at` · integer | null
- `crawl` · ImportDataStageStatus | null
- `extract` · ImportDataStageStatus | null
- `merge` · ImportDataStageStatus | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}/assets
List stored crawl assets for a website scrape
Operation id: `list_website_scrape_assets_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__assets_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_WebsiteScrapeAssetData__
- `request_id` · string · required
- `success` · true
- `data` · WebsiteScrapeAssetData[] · required
- `id` · string · required
- `page_id` · string | null
- `asset_url` · string · required
- `asset_host` · string | null
- `asset_type` · string · required
- `mime_type` · string | null
- `alt_text` · string | null
- `source_page_url` · string | null
- `width` · integer | null
- `height` · integer | null
- `byte_size` · integer | null
- `checksum_sha256` · string | null
- `download_status` · string · required
- `download_error` · string | null
- `storage_status` · string · required
- `storage_error` · string | null
- `storage_provider` · string | null
- `storage_bucket` · string | null
- `storage_blob_name` · string | null
- `storage_object_url` · string | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}/extract
Re-run extraction on stored website scrape pages
Operation id: `rerun_website_scrape_extraction_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__extract_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ReExtractResponseData_
- `request_id` · string · required
- `success` · true
- `data` · ReExtractResponseData · required
- `run_id` · string · required
- `status` · string · required
- `attempt_id` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}/extraction
Get the latest website scrape extraction payload
Operation id: `get_website_scrape_extraction_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__extraction_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteScrapeExtractionData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteScrapeExtractionData · required
- `attempt_id` · string · required
- `run_id` · string · required
- `status` · "queued" | "running" | "completed" | "failed" · required
- `extractor_version` · string · required
- `started_at` · integer | null
- `completed_at` · integer | null
- `payload` · ExtractionPayloadData
- `summary` · object | null
- `error_message` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}/merge
Apply a selected subset of extracted data into the business record
Operation id: `apply_website_scrape_merge_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__merge_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): MergeSelectionBody
- `extract_attempt_id` · string · required
- `business_profile` · MergeBusinessProfileSelection | null
- `scalars` · string[]
- `contact_info` · string[]
- `address` · boolean
- `descriptions` · string[]
- `social_profiles` · string[]
- `services` · integer[]
- `packages` · integer[]
- `service_items` · integer[]
- `offers` · integer[]
- `jobs` · integer[]
- `faqs` · integer[]
- `locations` · integer[]
- `team_members` · integer[]
- `blog_posts` · integer[]
- `blog_post_authors` · integer[]
- `blog_post_tags` · integer[]
- `contexts` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_MergeApplyResponseData_
- `request_id` · string · required
- `success` · true
- `data` · MergeApplyResponseData · required
- `run_id` · string · required
- `status` · string · required
- `merge_summary` · MergeSummaryData · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}/pages
List stored crawl pages for a website scrape
Operation id: `list_website_scrape_pages_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__pages_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_WebsiteScrapePageData__
- `request_id` · string · required
- `success` · true
- `data` · WebsiteScrapePageData[] · required
- `id` · string · required
- `url` · string · required
- `normalized_url` · string · required
- `title` · string | null
- `http_status` · integer | null
- `content_type` · string | null
- `depth` · integer · required
- `markdown` · string | null
- `cleaned_html` · string | null
- `raw_text` · string | null
- `metadata_json` · object | null
- `links_json` · object | null
- `images_json` · object[] | null
- `crawl_error` · string | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}/pages/{page_id}
Get a stored crawl page
Operation id: `get_website_scrape_page_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__pages__page_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `page_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteScrapePageData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteScrapePageData · required
- `id` · string · required
- `url` · string · required
- `normalized_url` · string · required
- `title` · string | null
- `http_status` · integer | null
- `content_type` · string | null
- `depth` · integer · required
- `markdown` · string | null
- `cleaned_html` · string | null
- `raw_text` · string | null
- `metadata_json` · object | null
- `links_json` · object | null
- `images_json` · object[] | null
- `crawl_error` · string | null
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/import-data/website-scrapes/{scrape_run_id}/review
Get the merged review view for a website scrape
Operation id: `get_website_scrape_review_api_v1_admin_businesses__business_id__import_data_website_scrapes__scrape_run_id__review_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `scrape_run_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteScrapeReviewData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteScrapeReviewData · required
- `run` · WebsiteScrapeRunData · required
- `extraction` · WebsiteScrapeExtractionData | null
- `merge_summary` · MergeSummaryData | null
- `current_business_profile` · ExtractedBusinessProfileData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Industry
6 endpoints. HTML: https://developers.keystone.app/api/console/industry/
### GET /api/v1/admin/businesses/{business_id}/industries
Get business's primary and secondary industry
Operation id: `get_business_industries_api_v1_admin_businesses__business_id__industries_get`
Get business's primary and secondary industry (resolved to full industry data). 404 if business not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessIndustriesData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessIndustriesData · required: Business's primary and secondary industry (resolved to full industry data).
- `primary` · IndustryData | null
- `secondary` · IndustryData[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/industries
Update primary and/or secondary industry of business
Operation id: `patch_business_industries_api_v1_admin_businesses__business_id__industries_patch`
Update primary and/or secondary industry. 404 if business not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BusinessIndustriesPatchBody
- `primary` · string | null
- `secondary` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_BusinessIndustriesData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessIndustriesData · required: Business's primary and secondary industry (resolved to full industry data).
- `primary` · IndustryData | null
- `secondary` · IndustryData[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries
List all industries
Operation id: `list_industries_api_v1_admin_industries_get`
Get list of all industries (ordered by name).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_IndustryData__
- `request_id` · string · required
- `success` · true
- `data` · IndustryData[] · required
- `id` · string · required
- `industry_name` · string · required
- `industry_description` · string | null
- `industry_short_description` · string | null
- `accounts_count` · integer
- `stock_photos_count` · integer
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/industries
Create industry
Operation id: `create_industry_api_v1_admin_industries_post`
Create an industry with client-provided id.
Returns 201 on create; 409 when the industry id already exists.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): IndustryCreateBody
- `id` · string | null
- `name` · string · required
- `description` · string · required
**Responses**
- `201` Successful Response: SuccessResponse_IndustryData_
- `request_id` · string · required
- `success` · true
- `data` · IndustryData · required: Industry for list/GET response.
- `id` · string · required
- `industry_name` · string · required
- `industry_description` · string | null
- `industry_short_description` · string | null
- `accounts_count` · integer
- `stock_photos_count` · integer
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries/{industry_id}
Get industry by industry ID
Operation id: `get_industry_api_v1_admin_industries__industry_id__get`
Get one industry by id. 404 if missing.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_IndustryDetailsData_
- `request_id` · string · required
- `success` · true
- `data` · IndustryDetailsData · required: Industry for details (GET by id) response.
- `id` · string · required
- `industry_name` · string · required
- `industry_description` · string | null
- `industry_short_description` · string | null
- `accounts_count` · integer
- `stock_photos_count` · integer
- `updated_at` · integer · required
- `created_at` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/industries/{industry_id}
Update industry details
Operation id: `patch_industry_api_v1_admin_industries__industry_id__patch`
Partially update industry name and descriptions. 404 if industry not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): IndustryPatchBody
- `industry_name` · string | null
- `industry_description` · string | null
- `industry_short_description` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_IndustryData_
- `request_id` · string · required
- `success` · true
- `data` · IndustryData · required: Industry for list/GET response.
- `id` · string · required
- `industry_name` · string · required
- `industry_description` · string | null
- `industry_short_description` · string | null
- `accounts_count` · integer
- `stock_photos_count` · integer
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Integrations status
1 endpoints. HTML: https://developers.keystone.app/api/console/integrations-status/
### GET /api/v1/admin/businesses/{business_id}/integrations/status
Combined status for every integration on the console's rail
Operation id: `get_integrations_status_api_v1_admin_businesses__business_id__integrations_status_get`
All eight rail rows in one payload, always complete and in rail order.
Rows report ``unknown``/``not_configured`` where the signal is missing —
never a fabricated ``connected``. The two OAuth-backed rows (Meta, Google
Business Profile) separate "authenticated" from "actually usable" and put
the blocking reason in ``detail``, so an unaccepted GBP location-manager
invite reads as ``pending`` with the reason rather than as a green tick.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_IntegrationsStatusData_
- `request_id` · string · required
- `success` · true
- `data` · IntegrationsStatusData · required: The whole rail in one payload — the point of the endpoint. ``rows`` is ordered as the console renders it (Deployment, GitHub, Cloudflare, Meta, Google Business Profile, PostHog, Google Tag Manager, Google Search Console) and always carries every row, so the console never has to reason about which calls succeeded.
- `business_id` · string (uuid) · required
- `website_id` · string (uuid) | null
- `rows` · IntegrationStatusRow[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Jobs
7 endpoints. HTML: https://developers.keystone.app/api/console/jobs/
### GET /api/v1/admin/businesses/{business_id}/job_postings
Get all job postings
Operation id: `list_job_postings_api_v1_admin_businesses__business_id__job_postings_get`
Get all job postings for a business (ordered by display_order, then posted_at).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_JobPostingData__
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData[] · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/job_postings
Create job posting
Operation id: `create_job_posting_api_v1_admin_businesses__business_id__job_postings_post`
Create a new job posting.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): JobPostingCreateBody
- `title` · string · required
- `slug` · string | null
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus: Job posting status enum.
- `featured` · boolean
- `display_order` · integer | null
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_JobPostingData_
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData · required: Job posting for admin list/GET response.
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/job_postings/{job_posting_id}
Get job posting details
Operation id: `get_job_posting_api_v1_admin_businesses__business_id__job_postings__job_posting_id__get`
Get job posting by id. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_JobPostingData_
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData · required: Job posting for admin list/GET response.
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/job_postings/{job_posting_id}
Update job posting
Operation id: `update_job_posting_api_v1_admin_businesses__business_id__job_postings__job_posting_id__put`
Update job posting details. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): JobPostingUpdateBody
- `title` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType | null
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus | null
- `featured` · boolean | null
- `display_order` · integer | null
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_JobPostingData_
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData · required: Job posting for admin list/GET response.
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/job_postings/{job_posting_id}
Delete job posting
Operation id: `delete_job_posting_api_v1_admin_businesses__business_id__job_postings__job_posting_id__delete`
Soft delete a job posting. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/job_postings/{job_posting_id}/applications
List job applications for a posting
Operation id: `list_job_applications_api_v1_admin_businesses__business_id__job_postings__job_posting_id__applications_get`
List job applications for a specific job posting.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_JobApplicationData__
- `request_id` · string · required
- `success` · true
- `data` · JobApplicationData[] · required
- `id` · string · required
- `form_type` · string · required
- `data` · object · required
- `contact` · object | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/job_postings/reorder
Reorder job postings
Operation id: `reorder_job_postings_api_v1_admin_businesses__business_id__job_postings_reorder_post`
Reorder job postings by ordered list of job posting ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): JobPostingsReorderBody
- `ordered_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_JobPostingData__
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData[] · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Listing accounts
6 endpoints. HTML: https://developers.keystone.app/api/console/listing-accounts/
### POST /api/v1/admin/businesses/{business_id}/listings/{listing_id}/keystone-invite/retry
Retry accepting the Keystone location-manager invite for a listing
Operation id: `retry_keystone_invite_api_v1_admin_businesses__business_id__listings__listing_id__keystone_invite_retry_post`
Fire the durable accept worker for a listing whose Keystone manager invite is stuck
'pending'. Idempotent and non-blocking — the worker retries acceptance off-request.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/listings/{listing_id}/mapping
Map or unmap a listing to an internal SOR location
Operation id: `update_listing_mapping_api_v1_admin_businesses__business_id__listings__listing_id__mapping_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ListingInternalMappingBody
- `internal_location_id` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_ListingLocationData_
- `request_id` · string · required
- `success` · true
- `data` · ListingLocationData · required: One provider-backed listing (``listings`` table); exposed as a *location* in admin APIs.
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `listing_account_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `provider_listing_id` · string · required
- `provider_resource_name` · string · required
- `internal_location_id` · string (uuid) | null
- `title` · string | null
- `store_code` · string | null
- `location_metadata` · object | null
- `overview` · object | null
- `synced_at` · string (date-time) | null
- `is_managed` · boolean · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listings/accounts
List listing provider accounts
Operation id: `list_listing_accounts_api_v1_admin_businesses__business_id__listings_accounts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `provider` | query | ListingProvider \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ListingAccountData__
- `request_id` · string · required
- `success` · true
- `data` · ListingAccountData[] · required
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `connection_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `provider_account_id` · string · required
- `provider_account_name` · string | null
- `account_metadata` · object
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/listings/discovery/import
Discover and import provider accounts + listings for a business
Operation id: `import_listing_discovery_api_v1_admin_businesses__business_id__listings_discovery_import_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `connection_id` | query | string (uuid) | yes | OAuth connection used to call the provider API. |
| `provider` | query | ListingProvider | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ListingDiscoveryImportResponseData_
- `request_id` · string · required
- `success` · true
- `data` · ListingDiscoveryImportResponseData · required: Summary after POST ``…/listings/discovery/import``.
- `accounts_discovered` · integer · required
- `locations_discovered` · integer · required
- `locations_created_or_updated` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listings/locations
List provider listing locations for this business
Operation id: `list_listing_locations_api_v1_admin_businesses__business_id__listings_locations_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `provider` | query | ListingProvider \| null | no | |
| `is_managed` | query | boolean \| null | no | |
| `internal_location_id` | query | string (uuid) \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ListingLocationData__
- `request_id` · string · required
- `success` · true
- `data` · ListingLocationData[] · required
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `listing_account_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `provider_listing_id` · string · required
- `provider_resource_name` · string · required
- `internal_location_id` · string (uuid) | null
- `title` · string | null
- `store_code` · string | null
- `location_metadata` · object | null
- `overview` · object | null
- `synced_at` · string (date-time) | null
- `is_managed` · boolean · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/listings/selection
Replace which listings are managed for this business
Operation id: `save_managed_listing_selection_route_api_v1_admin_businesses__business_id__listings_selection_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ManagedListingSelectionBody
- `managed_listing_ids` · string (uuid)[] · required
- `auto_accept_keystone_invitations` · boolean: GBP: after inviting Keystone as location manager, poll and accept the pending invitation using Keystone agency OAuth (KEYSTONE_GBP_OAUTH_*). Set false to leave the invite pending for manual acceptance.
**Responses**
- `200` Successful Response: SuccessResponse_ManagedListingSelectionResponseData_
- `request_id` · string · required
- `success` · true
- `data` · ManagedListingSelectionResponseData · required: Payload for ``PATCH …/listings/selection`` — updated rows plus GBP side-effect hints.
- `listings` · ListingLocationData[] · required
- `accept_invite_success` · boolean · required: When ``auto_accept_keystone_invitations`` was true: false if any accept attempt failed (API error or no matching invite after retries). True when auto-accept was false or every newly managed listing accepted successfully.
- `configure_notification_success` · boolean · required: False if any ``enable_notifications`` (after newly managed) or ``disable_notifications`` (after last unmanaged on an account) raised; errors are logged only.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Listing reviews
6 endpoints. HTML: https://developers.keystone.app/api/console/listing-reviews/
### GET /api/v1/admin/businesses/{business_id}/listing-reviews
List listing reviews (optionally scoped to one listing)
Operation id: `list_listing_reviews_all_api_v1_admin_businesses__business_id__listing_reviews_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | query | string (uuid) \| null | no | When set, only reviews for this listing id. |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `sort_by` | query | string | no | |
| `rating_min` | query | integer \| null | no | |
| `rating_max` | query | integer \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ListingReviewData__
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewData[] · required
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `listing_id` · string (uuid) · required
- `provider_review_id` · string · required
- `provider_resource_name` · string · required
- `reviewer_display_name` · string | null · required
- `reviewer_profile_photo_url` · string | null · required
- `reviewer_is_anonymous` · boolean · required
- `rating` · integer | null · required
- `body` · string | null · required
- `language` · string | null · required
- `permalink_url` · string | null · required
- `provider_created_at` · string (date-time) | null · required
- `provider_updated_at` · string (date-time) | null · required
- `status` · ListingReviewStatus · required: Review workflow status for ``listing_reviews``.
- `review_metadata` · object | null · required
- `is_featured` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listing-reviews/{review_id}
Get one listing review
Operation id: `get_listing_review_admin_api_v1_admin_businesses__business_id__listing_reviews__review_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ListingReviewData_
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewData · required: One row from ``listing_reviews`` (provider review keyed by ``listings.id``).
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `listing_id` · string (uuid) · required
- `provider_review_id` · string · required
- `provider_resource_name` · string · required
- `reviewer_display_name` · string | null · required
- `reviewer_profile_photo_url` · string | null · required
- `reviewer_is_anonymous` · boolean · required
- `rating` · integer | null · required
- `body` · string | null · required
- `language` · string | null · required
- `permalink_url` · string | null · required
- `provider_created_at` · string (date-time) | null · required
- `provider_updated_at` · string (date-time) | null · required
- `status` · ListingReviewStatus · required: Review workflow status for ``listing_reviews``.
- `review_metadata` · object | null · required
- `is_featured` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/listing-reviews/{review_id}
Update listing review (e.g. featured flag)
Operation id: `patch_listing_review_featured_api_v1_admin_businesses__business_id__listing_reviews__review_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ListingReviewFeaturedUpdateBody
- `is_featured` · boolean · required
**Responses**
- `200` Successful Response: SuccessResponse_ListingReviewData_
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewData · required: One row from ``listing_reviews`` (provider review keyed by ``listings.id``).
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `listing_id` · string (uuid) · required
- `provider_review_id` · string · required
- `provider_resource_name` · string · required
- `reviewer_display_name` · string | null · required
- `reviewer_profile_photo_url` · string | null · required
- `reviewer_is_anonymous` · boolean · required
- `rating` · integer | null · required
- `body` · string | null · required
- `language` · string | null · required
- `permalink_url` · string | null · required
- `provider_created_at` · string (date-time) | null · required
- `provider_updated_at` · string (date-time) | null · required
- `status` · ListingReviewStatus · required: Review workflow status for ``listing_reviews``.
- `review_metadata` · object | null · required
- `is_featured` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listing-reviews/{review_id}/replies
List replies for a listing review
Operation id: `list_listing_review_replies_api_v1_admin_businesses__business_id__listing_reviews__review_id__replies_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ListingReviewReplyData__
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewReplyData[] · required
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `listing_review_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `provider_reply_id` · string | null
- `parent_reply_id` · string (uuid) | null
- `thread_root_reply_id` · string (uuid) | null
- `author_type` · ListingReplyAuthorType · required: Reply author type for ``listing_review_replies``.
- `provider_author_id` · string | null
- `body` · string | null
- `provider_created_at` · string (date-time) | null
- `provider_updated_at` · string (date-time) | null
- `is_deleted` · boolean · required
- `reply_metadata` · object | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/listing-reviews/{review_id}/reply
Create or update owner reply for a listing review
Operation id: `upsert_listing_review_owner_reply_api_v1_admin_businesses__business_id__listing_reviews__review_id__reply_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ListingReviewReplyUpsertBody
- `comment` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_ListingReviewReplyData_
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewReplyData · required: One row from ``listing_review_replies``.
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `listing_review_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `provider_reply_id` · string | null
- `parent_reply_id` · string (uuid) | null
- `thread_root_reply_id` · string (uuid) | null
- `author_type` · ListingReplyAuthorType · required: Reply author type for ``listing_review_replies``.
- `provider_author_id` · string | null
- `body` · string | null
- `provider_created_at` · string (date-time) | null
- `provider_updated_at` · string (date-time) | null
- `is_deleted` · boolean · required
- `reply_metadata` · object | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/listing-reviews/{review_id}/reply
Delete owner reply for a listing review
Operation id: `delete_listing_review_owner_reply_api_v1_admin_businesses__business_id__listing_reviews__review_id__reply_delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Listings
11 endpoints. HTML: https://developers.keystone.app/api/console/listings/
### GET /api/v1/admin/businesses/{business_id}/listings
List managed GBP listings grouped by internal location
Operation id: `list_managed_listings_api_v1_admin_businesses__business_id__listings_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ManagedListingsData_
- `request_id` · string · required
- `success` · true
- `data` · ManagedListingsData · required
- `groups` · ListingLocationGroup[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listings/{listing_id}
Get managed listing detail (overview from GBP Business Information)
Operation id: `get_listing_detail_api_v1_admin_businesses__business_id__listings__listing_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ListingDetailData_
- `request_id` · string · required
- `success` · true
- `data` · ListingDetailData · required: Single managed listing for the Overview tab (``listing_id`` = ``gbp_locations.id``).
- `listing_id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `gbp_location_id` · string · required
- `full_resource_name` · string · required
- `internal_location_id` · string (uuid) | null · required
- `is_managed` · boolean · required
- `title` · string | null · required
- `store_code` · string | null · required
- `overview` · ListingOverviewData | null · required
- `platform_info` · ListingPlatformInfo | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listings/{listing_id}/photos
List photos linked to a managed GBP listing
Operation id: `get_listing_photos_api_v1_admin_businesses__business_id__listings__listing_id__photos_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ListingPhotoItem__
- `request_id` · string · required
- `success` · true
- `data` · ListingPhotoItem[] · required
- `association_id` · string (uuid) · required
- `photo_id` · string (uuid) · required
- `photo_url` · string | null
- `is_deleted` · boolean · required: Library/photo row soft-delete; association is still active until removed.
- `photo_source` · string · required
- `format` · string | null
- `width` · integer | null
- `height` · integer | null
- `duration` · number | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/listings/{listing_id}/photos
Add a library photo to a GBP listing (uploads to GBP then links)
Operation id: `add_listing_photo_api_v1_admin_businesses__business_id__listings__listing_id__photos_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AddListingPhotoBody
- `photo_id` · string (uuid) · required
**Responses**
- `200` Successful Response: SuccessResponse_ListingPhotoItem_
- `request_id` · string · required
- `success` · true
- `data` · ListingPhotoItem · required: One photo on a managed listing (active ``listing_media`` + ``photos`` row).
- `association_id` · string (uuid) · required
- `photo_id` · string (uuid) · required
- `photo_url` · string | null
- `is_deleted` · boolean · required: Library/photo row soft-delete; association is still active until removed.
- `photo_source` · string · required
- `format` · string | null
- `width` · integer | null
- `height` · integer | null
- `duration` · number | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/listings/{listing_id}/photos/{photo_id}
Remove a photo from the listing (association soft-delete only)
Operation id: `remove_listing_photo_api_v1_admin_businesses__business_id__listings__listing_id__photos__photo_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `photo_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/listings/{listing_id}/photos/sync
Enqueue GBP listing media import for one managed listing
Operation id: `sync_listing_photos_from_gbp_api_v1_admin_businesses__business_id__listings__listing_id__photos_sync_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ListingPhotosSyncBody
- `sync_type` · ListingSyncType: Sync run type for listing sync logs.
- `max_pages_per_channel` · integer | null: Max list API pages per channel (owner ``media.list`` and customer ``media.customers.list``); omit for all pages until exhausted.
- `page_size` · integer: Items per GBP list request (capped per provider).
**Responses**
- `200` Successful Response: SuccessResponse_ListingPhotosSyncEnqueuedData_
- `request_id` · string · required
- `success` · true
- `data` · ListingPhotosSyncEnqueuedData · required: Admin API response after enqueueing GBP listing media import (same pattern as review sync enqueue).
- `gbp_location_row_id` · string (uuid) · required
- `sync_log_id` · string (uuid) · required
- `sync_type` · ListingSyncType · required: Sync run type for listing sync logs.
- `title` · string | null
- `enqueued` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/listings/{listing_id}/photos/sync-logs/{sync_log_id}/resume
Resume or retry a PARTIAL/FAILED GBP listing media sync log (background)
Operation id: `resume_listing_photos_sync_from_gbp_api_v1_admin_businesses__business_id__listings__listing_id__photos_sync_logs__sync_log_id__resume_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `sync_log_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ListingPhotosSyncResumeBody
- `max_pages_per_channel` · integer | null: Cap GBP list pages **per channel** for this resume segment.
- `page_size` · integer: Items per GBP list request.
**Responses**
- `200` Successful Response: SuccessResponse_ListingPhotosSyncResumeEnqueuedData_
- `request_id` · string · required
- `success` · true
- `data` · ListingPhotosSyncResumeEnqueuedData · required: Admin API response after accepting resume/retry for a listing media sync log.
- `gbp_location_row_id` · string (uuid) · required
- `sync_log_id` · string (uuid) · required
- `title` · string | null
- `enqueued` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listings/{listing_id}/reviews
List provider reviews stored for a managed listing
Operation id: `list_listing_reviews_api_v1_admin_businesses__business_id__listings__listing_id__reviews_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `sort_by` | query | string | no | |
| `rating_min` | query | integer \| null | no | |
| `rating_max` | query | integer \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ListingReviewData__
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewData[] · required
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `provider` · ListingProvider · required: Listing integration surface (``listing_accounts``, ``listings``, …). Distinct from ``OAuthProvider``.
- `listing_id` · string (uuid) · required
- `provider_review_id` · string · required
- `provider_resource_name` · string · required
- `reviewer_display_name` · string | null · required
- `reviewer_profile_photo_url` · string | null · required
- `reviewer_is_anonymous` · boolean · required
- `rating` · integer | null · required
- `body` · string | null · required
- `language` · string | null · required
- `permalink_url` · string | null · required
- `provider_created_at` · string (date-time) | null · required
- `provider_updated_at` · string (date-time) | null · required
- `status` · ListingReviewStatus · required: Review workflow status for ``listing_reviews``.
- `review_metadata` · object | null · required
- `is_featured` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/listings/{listing_id}/reviews/sync
Enqueue provider review sync for one managed listing
Operation id: `sync_listing_reviews_api_v1_admin_businesses__business_id__listings__listing_id__reviews_sync_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ListingReviewsSyncBody
- `sync_type` · ListingSyncType: Sync run type for listing sync logs.
- `max_pages` · integer | null: Cap provider list pages for this run (omit for natural stop).
- `page_size` · integer: Reviews per list page.
**Responses**
- `200` Successful Response: SuccessResponse_ListingReviewsSyncEnqueuedData_
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewsSyncEnqueuedData · required
- `listing_id` · string (uuid) · required
- `sync_log_id` · string (uuid) · required
- `sync_type` · ListingSyncType · required: Sync run type for listing sync logs.
- `title` · string | null
- `enqueued` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/listings/{listing_id}/reviews/sync-logs/{sync_log_id}/resume
Resume or retry a PARTIAL/FAILED listing review sync log
Operation id: `resume_listing_reviews_sync_api_v1_admin_businesses__business_id__listings__listing_id__reviews_sync_logs__sync_log_id__resume_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `sync_log_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ListingReviewsSyncResumeBody
- `max_pages` · integer | null
- `page_size` · integer
**Responses**
- `200` Successful Response: SuccessResponse_ListingReviewsSyncResumeEnqueuedData_
- `request_id` · string · required
- `success` · true
- `data` · ListingReviewsSyncResumeEnqueuedData · required
- `listing_id` · string (uuid) · required
- `sync_log_id` · string (uuid) · required
- `title` · string | null
- `enqueued` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/listings/gbp/refresh-all
Re-fetch overview + platform metadata for EVERY GBP listing (all businesses)
Operation id: `refresh_all_gbp_listings_api_v1_admin_listings_gbp_refresh_all_post`
Fleet-wide backfill: pull each GBP listing's live overview + ``metadata``
block (``placeId`` / ``mapsUri`` / ``newReviewUri``) and persist it. One call
fixes every business whose ``location_metadata`` was frozen stale at discovery
time, so the conversational agent's Google-review link resolves per business
(KS-866). Returns ``{total, refreshed, failed}``; per-listing failures are
isolated (savepoint) and do not abort the run.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Listings agent
2 endpoints. HTML: https://developers.keystone.app/api/console/listings-agent/
### GET /api/v1/businesses/{business_id}/listings-agent/config
Get effective listings-agent settings for a business
Operation id: `get_config_api_v1_businesses__business_id__listings_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SimpleAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SimpleAgentConfigData · required: Shared shape for contacts / ads / listings agents.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/listings-agent/config
Update listings-agent settings for a business
Operation id: `patch_config_api_v1_businesses__business_id__listings_agent_config_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SimpleAgentConfigPatchBody
- `enabled` · boolean | null
- `instructions` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SimpleAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SimpleAgentConfigData · required: Shared shape for contacts / ads / listings agents.
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Locations
6 endpoints. HTML: https://developers.keystone.app/api/console/locations/
### GET /api/v1/admin/businesses/{business_id}/locations
Get all locations
Operation id: `list_locations_api_v1_admin_businesses__business_id__locations_get`
Get all locations for a business. By default only active; set listInactive=true to include inactive.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `list_inactive` | query | boolean | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_LocationData__
- `request_id` · string · required
- `success` · true
- `data` · LocationData[] · required
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/locations
Create location
Operation id: `create_location_api_v1_admin_businesses__business_id__locations_post`
Create a new location.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): LocationCreateBody
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `monday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `tuesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `wednesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `thursday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `friday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `saturday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `sunday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `slug` · string | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_LocationData_
- `request_id` · string · required
- `success` · true
- `data` · LocationData · required: Location for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/locations/{location_id}
Get location details
Operation id: `get_location_api_v1_admin_businesses__business_id__locations__location_id__get`
Get location by id. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `location_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_LocationData_
- `request_id` · string · required
- `success` · true
- `data` · LocationData · required: Location for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/locations/{location_id}
Update location
Operation id: `update_location_api_v1_admin_businesses__business_id__locations__location_id__put`
Update location details. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `location_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): LocationUpdateBody
- `location_name` · string | null
- `is_primary_location` · boolean | null
- `is_active_location` · boolean | null
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `monday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `tuesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `wednesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `thursday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `friday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `saturday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `sunday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `slug` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_LocationData_
- `request_id` · string · required
- `success` · true
- `data` · LocationData · required: Location for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/locations/{location_id}
Delete location
Operation id: `delete_location_api_v1_admin_businesses__business_id__locations__location_id__delete`
Delete a location. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `location_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/locations/reorder
Reorder locations
Operation id: `reorder_locations_api_v1_admin_businesses__business_id__locations_reorder_put`
Reorder locations by ordered list of location ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): LocationReorderBody
- `ordered_location_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_LocationData__
- `request_id` · string · required
- `success` · true
- `data` · LocationData[] · required
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Maps
5 endpoints. HTML: https://developers.keystone.app/api/console/maps/
### GET /api/v1/admin/businesses/{business_id}/listings/{listing_id}/maps/performance
Per-listing Performance tab: 30-day metrics, keywords, tracked queries
Operation id: `get_performance_api_v1_admin_businesses__business_id__listings__listing_id__maps_performance_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PerformanceData_
- `request_id` · string · required
- `success` · true
- `data` · PerformanceData · required
- `businessId` · string (uuid) · required
- `listingId` · string (uuid) · required
- `generatedAt` · string (date-time) · required
- `windowDays` · integer · required
- `state` · "ok" | "syncing" | "forbidden" | "rankings_disabled" | "unavailable" · required
- `groups` · MetricGroup[] · required
- `keywordsMonth` · string (date) | null
- `keywords` · KeywordRow[] · required
- `queriesState` · "ok" | "syncing" | "forbidden" | "rankings_disabled" | "unavailable" · required
- `queries` · TrackedQuery[] · required
- `volumeArea` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listings/{listing_id}/maps/profile-data
Per-listing Profile data tab: the audit inventory with rule hints
Operation id: `get_profile_data_api_v1_admin_businesses__business_id__listings__listing_id__maps_profile_data_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ProfileData_
- `request_id` · string · required
- `success` · true
- `data` · ProfileData · required
- `businessId` · string (uuid) · required
- `listingId` · string (uuid) · required
- `generatedAt` · string (date-time) · required
- `state` · "ok" | "syncing" | "forbidden" | "rankings_disabled" | "unavailable" · required
- `auditedOn` · string (date) | null
- `groups` · ProfileGroup[] · required
- `rawAvailable` · boolean
- `extra` · object | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/listings/{listing_id}/maps/readiness
Per-listing Readiness tab: score, categories, recommendations
Operation id: `get_readiness_api_v1_admin_businesses__business_id__listings__listing_id__maps_readiness_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ReadinessData_
- `request_id` · string · required
- `success` · true
- `data` · ReadinessData · required
- `businessId` · string (uuid) · required
- `listingId` · string (uuid) · required
- `generatedAt` · string (date-time) · required
- `state` · "ok" | "syncing" | "forbidden" | "rankings_disabled" | "unavailable" · required
- `scoredOn` · string (date) | null
- `overall` · MapsCategoryScore | null
- `categories` · MapsCategoryScore[] · required
- `recommendations` · Recommendation[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/listings/{listing_id}/maps/refresh
Populate Maps analytics for one listing now (audit → performance → quality → score)
Operation id: `refresh_listing_api_v1_admin_businesses__business_id__listings__listing_id__maps_refresh_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `listing_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `202` Successful Response: SuccessResponse_RefreshData_
- `request_id` · string · required
- `success` · true
- `data` · RefreshData · required
- `scheduled` · boolean · required
- `reason` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/maps/summary
Maps performance across all managed listings of a business
Operation id: `get_summary_api_v1_admin_businesses__business_id__maps_summary_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SummaryData_
- `request_id` · string · required
- `success` · true
- `data` · SummaryData · required
- `businessId` · string (uuid) · required
- `generatedAt` · string (date-time) · required
- `windowDays` · integer · required
- `trends` · Trend[] · required
- `rows` · MetricRow[] · required
- `groups` · MetricGroup[] · required
- `listings` · ListingSummary[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Meta Conversions API
4 endpoints. HTML: https://developers.keystone.app/api/console/meta-capi/
### GET /api/v1/admin/businesses/{business_id}/integrations/meta/capi
Get Meta CAPI config for a business
Operation id: `get_meta_capi_config_api_v1_admin_businesses__business_id__integrations_meta_capi_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_MetaCapiConfigData_
- `request_id` · string · required
- `success` · true
- `data` · MetaCapiConfigData · required: CAPI config returned to admins. No token field — CAPI reuses the business' existing Meta OAuth token.
- `pixel_id` · string | null
- `test_event_code` · string | null
- `enabled` · boolean
- `connected` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/integrations/meta/capi
Set Meta CAPI config (pixel id, enable, test event code)
Operation id: `put_meta_capi_config_api_v1_admin_businesses__business_id__integrations_meta_capi_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): MetaCapiConfigBody
- `pixel_id` · string · required
- `test_event_code` · string | null
- `enabled` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_MetaCapiConfigData_
- `request_id` · string · required
- `success` · true
- `data` · MetaCapiConfigData · required: CAPI config returned to admins. No token field — CAPI reuses the business' existing Meta OAuth token.
- `pixel_id` · string | null
- `test_event_code` · string | null
- `enabled` · boolean
- `connected` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/integrations/meta/capi/pixels
Discover Meta pixels on a business' connected ad account
Operation id: `list_meta_capi_pixels_api_v1_admin_businesses__business_id__integrations_meta_capi_pixels_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_MetaCapiPixelsData_
- `request_id` · string · required
- `success` · true
- `data` · MetaCapiPixelsData · required: Pixels available on the business' connected Meta ad account.
- `pixels` · MetaPixelData[] · required
- `connected` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/integrations/meta/capi/test
Fire a test CAPI event (validate sender via Events Manager Test Events)
Operation id: `fire_meta_capi_test_api_v1_admin_businesses__business_id__integrations_meta_capi_test_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): MetaCapiTestBody
- `event_name` · string
- `email` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_MetaCapiTestResult_
- `request_id` · string · required
- `success` · true
- `data` · MetaCapiTestResult · required: Echo of Meta's /events response for a test fire.
- `events_received` · integer
- `fbtrace_id` · string | null
- `messages` · string[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: OAuth connections
4 endpoints. HTML: https://developers.keystone.app/api/console/oauth-connections/
### GET /api/v1/admin/businesses/{business_id}/connections
List active OAuth connections for a business
Operation id: `list_connections_api_v1_admin_businesses__business_id__connections_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `provider` | query | OAuthProvider \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_OAuthConnectionsListData_
- `request_id` · string · required
- `success` · true
- `data` · OAuthConnectionsListData · required
- `connections` · OAuthConnectionData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/connections/{connection_id}
Disconnect an OAuth connection
Operation id: `disconnect_connection_api_v1_admin_businesses__business_id__connections__connection_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `connection_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/connections/oauth/{provider}/start
Start OAuth for an authority (e.g. Google)
Operation id: `oauth_start_api_v1_admin_businesses__business_id__connections_oauth__provider__start_post`
Return an authorization URL for the given OAuth authority (admin UI redirect).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `provider` | path | OAuthProvider | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): OAuthStartBody
- `redirect_uri` · string | null: Optional redirect URI; must match a Google-authorized callback when using Google.
**Responses**
- `200` Successful Response: SuccessResponse_OAuthStartResponseData_
- `request_id` · string · required
- `success` · true
- `data` · OAuthStartResponseData · required
- `authorization_url` · string · required
- `state` · string · required
- `connection_id` · string (uuid) | null: Present when exactly one active connection exists for this business and provider.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/connections/oauth/{provider}/callback
OAuth callback (browser redirect target)
Operation id: `oauth_callback_api_v1_admin_connections_oauth__provider__callback_get`
Exchange authorization code for tokens and upsert ``oauth_connections`` (no JWT: IDP redirect).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `provider` | path | OAuthProvider | yes | |
| `code` | query | string \| null | no | |
| `state` | query | string \| null | no | |
| `error` | query | string \| null | no | |
| `error_description` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Offers
5 endpoints. HTML: https://developers.keystone.app/api/console/offers/
### PUT /api/v1/admin/businesses/{business_id}/offers/{offer_id}
Update an offer
Operation id: `update_offer_api_v1_admin_businesses__business_id__offers__offer_id__put`
Update an offer's fields. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `offer_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): OfferUpdateBody
- `offer_name` · string | null
- `offer_description` · string | null
- `value_terms` · string | null
- `expires_at` · integer | null
- `sort_order` · integer | null
- `active` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_OfferData_
- `request_id` · string · required
- `success` · true
- `data` · OfferData · required: Offer for response. Nested under PackageData.offers and ServiceItemData.offers.
- `id` · string · required
- `business_id` · string · required
- `offer_name` · string · required
- `offer_description` · string | null
- `value_terms` · string | null
- `expires_at` · integer | null
- `sort_order` · integer
- `active` · boolean
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/packages/{package_id}/offers
Attach a new offer to a package
Operation id: `add_offer_to_package_api_v1_admin_businesses__business_id__packages__package_id__offers_post`
Create an offer and link it to the package. 404 if package not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): OfferCreateBody
- `offer_name` · string · required
- `offer_description` · string | null
- `value_terms` · string | null
- `expires_at` · integer | null
- `sort_order` · integer
- `active` · boolean
**Responses**
- `201` Successful Response: SuccessResponse_OfferData_
- `request_id` · string · required
- `success` · true
- `data` · OfferData · required: Offer for response. Nested under PackageData.offers and ServiceItemData.offers.
- `id` · string · required
- `business_id` · string · required
- `offer_name` · string · required
- `offer_description` · string | null
- `value_terms` · string | null
- `expires_at` · integer | null
- `sort_order` · integer
- `active` · boolean
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/packages/{package_id}/offers/{offer_id}
Detach an offer from a package
Operation id: `remove_offer_from_package_api_v1_admin_businesses__business_id__packages__package_id__offers__offer_id__delete`
Unlink an offer from a package (and reap it if now orphaned). 404 if not linked.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `offer_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/service-items/{service_item_id}/offers
Attach a new offer to a service item
Operation id: `add_offer_to_service_item_api_v1_admin_businesses__business_id__service_items__service_item_id__offers_post`
Create an offer and link it to the service item. 404 if service item not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): OfferCreateBody
- `offer_name` · string · required
- `offer_description` · string | null
- `value_terms` · string | null
- `expires_at` · integer | null
- `sort_order` · integer
- `active` · boolean
**Responses**
- `201` Successful Response: SuccessResponse_OfferData_
- `request_id` · string · required
- `success` · true
- `data` · OfferData · required: Offer for response. Nested under PackageData.offers and ServiceItemData.offers.
- `id` · string · required
- `business_id` · string · required
- `offer_name` · string · required
- `offer_description` · string | null
- `value_terms` · string | null
- `expires_at` · integer | null
- `sort_order` · integer
- `active` · boolean
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/service-items/{service_item_id}/offers/{offer_id}
Detach an offer from a service item
Operation id: `remove_offer_from_service_item_api_v1_admin_businesses__business_id__service_items__service_item_id__offers__offer_id__delete`
Unlink an offer from a service item (and reap it if now orphaned). 404 if not linked.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_item_id` | path | string (uuid) | yes | |
| `offer_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Packages
9 endpoints. HTML: https://developers.keystone.app/api/console/packages/
### GET /api/v1/admin/businesses/{business_id}/packages
Get all packages
Operation id: `list_packages_api_v1_admin_businesses__business_id__packages_get`
Get all packages for a business (ordered by display_order). Member service items not included.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PackageData__
- `request_id` · string · required
- `success` · true
- `data` · PackageData[] · required
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/packages
Create package
Operation id: `create_package_api_v1_admin_businesses__business_id__packages_post`
Create a new package.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PackageCreateBody
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[]
- `is_featured` · boolean | null
**Responses**
- `201` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/packages/{package_id}
Get package details
Operation id: `get_package_api_v1_admin_businesses__business_id__packages__package_id__get`
Get package by id with member service items. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/packages/{package_id}
Update package
Operation id: `update_package_api_v1_admin_businesses__business_id__packages__package_id__put`
Update package details. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PackageUpdateBody
- `package_name` · string | null
- `package_description` · string | null
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[] | null
- `is_featured` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/packages/{package_id}
Delete package
Operation id: `delete_package_api_v1_admin_businesses__business_id__packages__package_id__delete`
Delete a package. Member service items survive (join rows cascade). 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/packages/{package_id}/service-items
Set package member service items
Operation id: `set_package_service_items_api_v1_admin_businesses__business_id__packages__package_id__service_items_put`
Replace the package's member service items (and their in-package order). 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PackageServiceItemsBody
- `ordered_service_item_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/packages/{package_id}/service-items/{service_item_id}
Add service item to package
Operation id: `add_service_item_to_package_api_v1_admin_businesses__business_id__packages__package_id__service_items__service_item_id__post`
Add a service item to a package (appended last). 404 if package not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `service_item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/packages/{package_id}/service-items/{service_item_id}
Remove service item from package
Operation id: `remove_service_item_from_package_api_v1_admin_businesses__business_id__packages__package_id__service_items__service_item_id__delete`
Remove a service item from a package. 404 if package or membership not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `service_item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/packages/reorder
Reorder packages
Operation id: `reorder_packages_api_v1_admin_businesses__business_id__packages_reorder_put`
Reorder packages by ordered list of package ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PackageReorderBody
- `ordered_package_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_PackageData__
- `request_id` · string · required
- `success` · true
- `data` · PackageData[] · required
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Photos
28 endpoints. HTML: https://developers.keystone.app/api/console/photos/
### GET /api/v1/admin/businesses/{business_id}/generated
List generated photos for a business
Operation id: `list_generated_photos_api_v1_admin_businesses__business_id__generated_get`
List all generated photos across generation jobs. Pagination via cursor and limit.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_GeneratedPhotoData__
- `request_id` · string · required
- `success` · true
- `data` · GeneratedPhotoData[] · required
- `id` · string · required: generated_photo_id (job_id:index)
- `business_id` · string · required
- `photo_source` · string
- `public_url` · string | null: Public CDN URL of the original (no auth); null when not CDN-addressable.
- `photo_metadata` · PhotoMetadata | null
- `generation_status` · string · required: Job status: queued \| processing \| completed \| failed \| stopped
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/generated/{generated_photo_id}
Serve generated photo file from GCS
Operation id: `get_generated_photo_file_api_v1_admin_businesses__business_id__generated__generated_photo_id__get`
Fetch generated photo bytes from GCS. generated_photo_id is 'job_id:index'.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `generated_photo_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: string (binary)
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/photos
Get business photo library
Operation id: `list_photos_api_v1_admin_businesses__business_id__photos_get`
Get business library photos. Pagination via cursor (string) and limit.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PhotoData__
- `request_id` · string · required
- `success` · true
- `data` · PhotoData[] · required
- `id` · string · required
- `business_id` · string · required
- `photo_url` · string | null
- `public_url` · string | null: Public CDN URL of the original (no auth); null when the photo isn't CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_source` · string · required
- `original_filename` · string | null
- `photo_metadata` · PhotoMetadata | null
- `is_deleted` · boolean
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/photos/{photo_id}
Get photo metadata
Operation id: `get_photo_metadata_api_v1_admin_businesses__business_id__photos__photo_id__get`
Metadata (incl. ``public_url``) for one photo — the JSON counterpart of the
byte-serving ``…/photos/get/{photo_id}``. 404 for missing, deleted, or
cross-tenant photos. Declared after the literal ``/photos/*`` routes so it
never shadows them.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `photo_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PhotoData_
- `request_id` · string · required
- `success` · true
- `data` · PhotoData · required: Photo for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `photo_url` · string | null
- `public_url` · string | null: Public CDN URL of the original (no auth); null when the photo isn't CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_source` · string · required
- `original_filename` · string | null
- `photo_metadata` · PhotoMetadata | null
- `is_deleted` · boolean
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/photos/{photo_id}
Delete photo from library
Operation id: `delete_photo_api_v1_admin_businesses__business_id__photos__photo_id__delete`
Soft-delete photo from business library. 404 if not found; 409 PHOTO_IN_USE
(with per-module usage breakdown in ``details``) if still referenced by a module.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `photo_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/photos/generate
Generate photos
Operation id: `generate_photos_api_v1_admin_businesses__business_id__photos_generate_post`
Generate photos from description/style. Returns job ID to poll for status.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): GeneratePhotosBody
- `description` · string · required
- `style` · string | null
- `aspect_ratio` · string | null
**Responses**
- `202` Successful Response: SuccessResponse_GenerateJobResponse_
- `request_id` · string · required
- `success` · true
- `data` · GenerateJobResponse · required: Response after submitting a photo generation job.
- `job_id` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/photos/generate/{job_id}
Get photo generation job status
Operation id: `get_generation_status_api_v1_admin_businesses__business_id__photos_generate__job_id__get`
Get generation job status.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_GenerateJobStatusData_
- `request_id` · string · required
- `success` · true
- `data` · GenerateJobStatusData · required: Generation job status.
- `job_id` · string · required
- `status` · "queued" | "processing" | "completed" | "failed" | "stopped" · required: queued \| processing \| completed \| failed \| stopped
- `created_at` · integer | null
- `generated_photo_ids` · string[] | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/photos/get/{photo_id}
Serve photo file from GCS
Operation id: `get_photo_file_api_v1_admin_businesses__business_id__photos_get__photo_id__get`
Fetch photo bytes from GCS and return as binary response. Requires viewer role.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `photo_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: string (binary)
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/photos/import-from-gallery
Import photos from industry gallery
Operation id: `import_from_gallery_api_v1_admin_businesses__business_id__photos_import_from_gallery_post`
Import selected industry gallery photos into business library.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ImportFromGalleryBody
- `gallery_photo_ids` · string[] · required
**Responses**
- `201` Successful Response: SuccessResponse_list_PhotoData__
- `request_id` · string · required
- `success` · true
- `data` · PhotoData[] · required
- `id` · string · required
- `business_id` · string · required
- `photo_url` · string | null
- `public_url` · string | null: Public CDN URL of the original (no auth); null when the photo isn't CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_source` · string · required
- `original_filename` · string | null
- `photo_metadata` · PhotoMetadata | null
- `is_deleted` · boolean
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/photos/import-from-unsplash
Import photos from Unsplash
Operation id: `import_from_unsplash_api_v1_admin_businesses__business_id__photos_import_from_unsplash_post`
Import photos from Unsplash by IDs or URLs (hotlinked; no upload). IDs require UNSPLASH_ACCESS_KEY.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ImportFromUnsplashBody
- `unsplash_photo_ids` · string[] | null
- `unsplash_photo_urls` · string[] | null
**Responses**
- `201` Successful Response: SuccessResponse_list_PhotoData__
- `request_id` · string · required
- `success` · true
- `data` · PhotoData[] · required
- `id` · string · required
- `business_id` · string · required
- `photo_url` · string | null
- `public_url` · string | null: Public CDN URL of the original (no auth); null when the photo isn't CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_source` · string · required
- `original_filename` · string | null
- `photo_metadata` · PhotoMetadata | null
- `is_deleted` · boolean
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/photos/import-generated
Import from generated photos
Operation id: `import_generated_api_v1_admin_businesses__business_id__photos_import_generated_post`
Import selected generated photos (by job_id:index) into business library.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ImportGeneratedBody
- `generated_photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_list_PhotoData__
- `request_id` · string · required
- `success` · true
- `data` · PhotoData[] · required
- `id` · string · required
- `business_id` · string · required
- `photo_url` · string | null
- `public_url` · string | null: Public CDN URL of the original (no auth); null when the photo isn't CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_source` · string · required
- `original_filename` · string | null
- `photo_metadata` · PhotoMetadata | null
- `is_deleted` · boolean
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/photos/industry-gallery
Get industry gallery photos
Operation id: `list_industry_gallery_api_v1_admin_businesses__business_id__photos_industry_gallery_get`
Get photos from industry gallery. Optional industryId filter.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `industry_id` | query | string \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_IndustryGalleryPhotoData__
- `request_id` · string · required
- `success` · true
- `data` · IndustryGalleryPhotoData[] · required
- `id` · string · required
- `industry_id` · string | null
- `photo_url` · string · required
- `public_url` · string | null: Public CDN URL of the original (no auth); null when not CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_metadata` · PhotoMetadata | null
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/photos/industry-gallery/get/{photo_id}
Serve industry gallery photo file from GCS
Operation id: `get_industry_gallery_photo_file_api_v1_admin_businesses__business_id__photos_industry_gallery_get__photo_id__get`
Fetch industry gallery photo bytes from GCS and return as binary response.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `photo_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: string (binary)
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/photos/search-unsplash
Search Unsplash
Operation id: `search_unsplash_api_v1_admin_businesses__business_id__photos_search_unsplash_get`
Search Unsplash. Requires UNSPLASH_ACCESS_KEY.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `query` | query | string | yes | Search term |
| `page` | query | integer | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_UnsplashSearchData_
- `request_id` · string · required
- `success` · true
- `data` · UnsplashSearchData · required: Unsplash search response (stub).
- `items` · UnsplashSearchResultItem[] · required
- `page` · integer
- `total_pages` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/photos/upload
Upload photo to business library
Operation id: `upload_photo_api_v1_admin_businesses__business_id__photos_upload_post`
Upload a photo (multipart/form-data). Requires GCS bucket configured.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (multipart/form-data, required): Body_upload_photo_api_v1_admin_businesses__business_id__photos_upload_post
- `file` · string · required: Image file
**Responses**
- `201` Successful Response: SuccessResponse_PhotoData_
- `request_id` · string · required
- `success` · true
- `data` · PhotoData · required: Photo for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `photo_url` · string | null
- `public_url` · string | null: Public CDN URL of the original (no auth); null when the photo isn't CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_source` · string · required
- `original_filename` · string | null
- `photo_metadata` · PhotoMetadata | null
- `is_deleted` · boolean
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries/{industry_id}/generated
List generated photos for an industry
Operation id: `list_industry_generated_photos_api_v1_admin_industries__industry_id__generated_get`
List all generated photos across generation jobs for an industry.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_IndustryGeneratedPhotoData__
- `request_id` · string · required
- `success` · true
- `data` · IndustryGeneratedPhotoData[] · required
- `id` · string · required: generated_photo_id (job_id:index)
- `industry_id` · string · required
- `photo_source` · string
- `public_url` · string | null: Public CDN URL of the original (no auth); null when not CDN-addressable.
- `photo_metadata` · PhotoMetadata | null
- `generation_status` · string · required: Job status: queued \| processing \| completed \| failed \| stopped
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries/{industry_id}/generated/{generated_photo_id}
Serve generated photo file from GCS
Operation id: `get_industry_generated_photo_file_api_v1_admin_industries__industry_id__generated__generated_photo_id__get`
Fetch generated photo bytes from GCS. generated_photo_id is 'job_id:index'.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `generated_photo_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries/{industry_id}/photos
List photos for an industry
Operation id: `list_industry_photos_api_v1_admin_industries__industry_id__photos_get`
List photos scoped to a single industry with cursor pagination.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_IndustryGalleryPhotoData__
- `request_id` · string · required
- `success` · true
- `data` · IndustryGalleryPhotoData[] · required
- `id` · string · required
- `industry_id` · string | null
- `photo_url` · string · required
- `public_url` · string | null: Public CDN URL of the original (no auth); null when not CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_metadata` · PhotoMetadata | null
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/industries/{industry_id}/photos
Delete industry gallery photos by id
Operation id: `delete_industry_gallery_photos_api_v1_admin_industries__industry_id__photos_delete`
Remove gallery rows whose ids are listed and belong to this industry (JSON body).
Body may be a JSON array of id strings or ``{"ids": [...]}`` (legacy ``gallery_photo_ids`` also accepted).
If you get 422 with an empty/missing body, some clients or proxies drop bodies on **DELETE**;
use ``POST .../photos/delete`` with the same JSON instead.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/industries/{industry_id}/photos/delete
Delete industry gallery photos (POST; use if DELETE body is stripped)
Operation id: `delete_industry_gallery_photos_via_post_api_v1_admin_industries__industry_id__photos_delete_post`
Same behavior as ``DELETE /industries/{industry_id}/photos``; POST keeps the JSON body on strict proxies.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/industries/{industry_id}/photos/generate
Generate photos
Operation id: `generate_industry_photos_api_v1_admin_industries__industry_id__photos_generate_post`
Generate photos from description/style for an industry. Returns job ID to poll for status.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): GeneratePhotosBody
- `description` · string · required
- `style` · string | null
- `aspect_ratio` · string | null
**Responses**
- `202` Successful Response: SuccessResponse_GenerateJobResponse_
- `request_id` · string · required
- `success` · true
- `data` · GenerateJobResponse · required: Response after submitting a photo generation job.
- `job_id` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries/{industry_id}/photos/generate/{job_id}
Get photo generation job status
Operation id: `get_industry_generation_status_api_v1_admin_industries__industry_id__photos_generate__job_id__get`
Get generation job status for an industry.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `job_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_GenerateJobStatusData_
- `request_id` · string · required
- `success` · true
- `data` · GenerateJobStatusData · required: Generation job status.
- `job_id` · string · required
- `status` · "queued" | "processing" | "completed" | "failed" | "stopped" · required: queued \| processing \| completed \| failed \| stopped
- `created_at` · integer | null
- `generated_photo_ids` · string[] | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/industries/{industry_id}/photos/import-from-unsplash
Import photos from Unsplash into industry gallery
Operation id: `import_industry_from_unsplash_api_v1_admin_industries__industry_id__photos_import_from_unsplash_post`
Import photos from Unsplash by IDs or URLs (hotlinked; no upload). IDs require UNSPLASH_ACCESS_KEY.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ImportFromUnsplashBody
- `unsplash_photo_ids` · string[] | null
- `unsplash_photo_urls` · string[] | null
**Responses**
- `201` Successful Response: SuccessResponse_list_IndustryGalleryPhotoData__
- `request_id` · string · required
- `success` · true
- `data` · IndustryGalleryPhotoData[] · required
- `id` · string · required
- `industry_id` · string | null
- `photo_url` · string · required
- `public_url` · string | null: Public CDN URL of the original (no auth); null when not CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_metadata` · PhotoMetadata | null
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/industries/{industry_id}/photos/import-generated
Import from generated photos
Operation id: `import_industry_generated_api_v1_admin_industries__industry_id__photos_import_generated_post`
Import selected generated photos (by job_id:index) into industry gallery.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ImportGeneratedBody
- `generated_photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_list_IndustryGalleryPhotoData__
- `request_id` · string · required
- `success` · true
- `data` · IndustryGalleryPhotoData[] · required
- `id` · string · required
- `industry_id` · string | null
- `photo_url` · string · required
- `public_url` · string | null: Public CDN URL of the original (no auth); null when not CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_metadata` · PhotoMetadata | null
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries/{industry_id}/photos/search-unsplash
Search Unsplash
Operation id: `search_unsplash_industry_api_v1_admin_industries__industry_id__photos_search_unsplash_get`
Search Unsplash for industry gallery workflows. Requires UNSPLASH_ACCESS_KEY.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `query` | query | string | yes | Search term |
| `page` | query | integer | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_UnsplashSearchData_
- `request_id` · string · required
- `success` · true
- `data` · UnsplashSearchData · required: Unsplash search response (stub).
- `items` · UnsplashSearchResultItem[] · required
- `page` · integer
- `total_pages` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/industries/{industry_id}/photos/template-defaults
List template default photos per website slot
Operation id: `list_industry_template_defaults_api_v1_admin_industries__industry_id__photos_template_defaults_get`
Return each slot from the default website_photos_config with optional linked gallery photo.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__TemplateDefaultSlotData__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/industries/{industry_id}/photos/template-defaults
Set or clear one template default photo slot
Operation id: `put_industry_template_defaults_api_v1_admin_industries__industry_id__photos_template_defaults_put`
Set one slot key to an industry gallery photo id, or null to clear it.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PutTemplateDefaultsBody
- `key` · string · required
- `photo_id` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__TemplateDefaultSlotData__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/industries/{industry_id}/photos/upload
Upload photo to industry gallery
Operation id: `upload_industry_photo_api_v1_admin_industries__industry_id__photos_upload_post`
Upload an industry gallery photo (multipart/form-data). Requires GCS bucket configured.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (multipart/form-data, required): Body_upload_industry_photo_api_v1_admin_industries__industry_id__photos_upload_post
- `file` · string · required: Image file
**Responses**
- `201` Successful Response: SuccessResponse_IndustryGalleryPhotoData_
- `request_id` · string · required
- `success` · true
- `data` · IndustryGalleryPhotoData · required: Industry gallery photo for list response.
- `id` · string · required
- `industry_id` · string | null
- `photo_url` · string · required
- `public_url` · string | null: Public CDN URL of the original (no auth); null when not CDN-addressable.
- `public_url_downloadable` · boolean | null: Whether browser fetch of public_url works (CORS). False for legacy R2 hosts (copy-only); null when public_url is null.
- `photo_metadata` · PhotoMetadata | null
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Platform admins
5 endpoints. HTML: https://developers.keystone.app/api/console/platform-admins/
### POST /api/v1/admin/platform-admin-invites/{invite_id}/accept
Accept a pending Keystone Admin invite for the authenticated user
Operation id: `accept_platform_admin_invite_api_v1_admin_platform_admin_invites__invite_id__accept_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `invite_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PlatformAdminUserData_
- `request_id` · string · required
- `success` · true
- `data` · PlatformAdminUserData · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/users
List Keystone Admins and pending admin invites
Operation id: `list_platform_admins_api_v1_admin_users_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PlatformAdminUserData__
- `request_id` · string · required
- `success` · true
- `data` · PlatformAdminUserData[] · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/users
Invite a Keystone Admin (pending invite + email)
Operation id: `invite_platform_admin_api_v1_admin_users_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PlatformAdminInviteBody
- `email` · string (email) · required
- `phone` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_PlatformAdminUserData_
- `request_id` · string · required
- `success` · true
- `data` · PlatformAdminUserData · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/users/{entry_id}
Update a Keystone Admin or pending admin invite
Operation id: `update_platform_admin_api_v1_admin_users__entry_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PlatformAdminUpdateBody
- `first_name` · string · required
- `last_name` · string · required
- `email` · string (email) · required
- `phone` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_PlatformAdminUserData_
- `request_id` · string · required
- `success` · true
- `data` · PlatformAdminUserData · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/users/{entry_id}
Remove pending admin invite or demote a Keystone Admin
Operation id: `delete_platform_admin_api_v1_admin_users__entry_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entry_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Pricing
7 endpoints. HTML: https://developers.keystone.app/api/console/pricing/
### GET /api/v1/admin/pricing/books
List price books (platform admin)
Operation id: `list_price_books_api_v1_admin_pricing_books_get`
Every book with its current version and the plans pointing at it. The live-subscriber count on each
plan is the blast radius of an edit — and the only place a `price_book_id` is discoverable at all.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PriceBooksData_
- `request_id` · string · required
- `success` · true
- `data` · PriceBooksData · required
- `price_books` · PriceBookData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/pricing/books/{book_id}/versions
List a price book's versions (platform admin)
Operation id: `list_price_book_versions_api_v1_admin_pricing_books__book_id__versions_get`
Every version, newest first, with full contents — enough to diff any two, or to republish an old
one as the newest, which is what a rollback is.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `book_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PriceBookVersionsData_
- `request_id` · string · required
- `success` · true
- `data` · PriceBookVersionsData · required
- `versions` · PriceBookVersionData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/pricing/books/{book_id}/versions
Publish a price book version (platform admin)
Operation id: `create_price_book_version_api_v1_admin_pricing_books__book_id__versions_post`
Publish the next version. Holds authorized after it commits price against the new version; every
hold already open keeps the one it was opened under and settles at that one's prices, so an edit can
never reprice work a customer has already started.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `book_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CreateVersionBody
- `rules` · object · required
- `markup_multiplier` · string · required
- `expected_version` · integer · required
- `reason` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_PriceBookVersionData_
- `request_id` · string · required
- `success` · true
- `data` · PriceBookVersionData · required: One publish: the complete snapshot that priced every usage row pinned to it. `credit_dollar_value` is the same in every version of a book — frozen for its life — and rides along so a version explains a charge on its own.
- `version_id` · string (uuid) · required
- `price_book_id` · string (uuid) · required
- `version` · integer · required
- `credit_dollar_value` · string · required
- `markup_multiplier` · string · required
- `rules` · object · required
- `created_by` · string | null
- `reason` · string
- `created_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/pricing/dimensions
List rating dimensions (platform admin)
Operation id: `list_rating_dimensions_api_v1_admin_pricing_dimensions_get`
The quantities the rater understands and the rate-sheet key each is priced under — a code constant
served so the editor's field-level validation cannot drift from the code that does the pricing. It says
nothing about which sheet is in force.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_RatingDimensionsData_
- `request_id` · string · required
- `success` · true
- `data` · RatingDimensionsData · required
- `dimensions` · RatingDimensionData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/pricing/rates
List vendor rate sheets (platform admin)
Operation id: `list_rate_sheets_api_v1_admin_pricing_rates_get`
Newest first, full tables inline — one call serves the list, a row's detail and the editor's
starting point (the row payments marks `in_force`).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_RateSheetsData_
- `request_id` · string · required
- `success` · true
- `data` · RateSheetsData · required
- `rate_sheets` · RateSheetData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/pricing/rates
Schedule a vendor rate sheet (platform admin)
Operation id: `create_rate_sheet_api_v1_admin_pricing_rates_post`
Schedule a complete new sheet for a future instant. It is a draft until then: settle cannot see it,
and it can still be edited. Only one may be pending at a time.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): RateSheetCreateBody
- `name` · string · required
- `rates` · object · required
- `effective_from` · string (date-time) · required
- `reason` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_RateSheetData_
- `request_id` · string · required
- `success` · true
- `data` · RateSheetData · required: A vendor rate table and when it takes effect. `status` is computed by payments against its own clock, so the console picks the live row off the list rather than comparing timestamps itself. A draft is rewritten in place, so `created_at` is when it was opened and `updated_at` when it was last written.
- `rates_id` · string (uuid) · required
- `name` · string | null
- `rates` · object · required
- `effective_from` · string (date-time) | null
- `status` · string · required
- `created_by` · string | null
- `reason` · string | null
- `created_at` · string (date-time) | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/pricing/rates/{rates_id}
Edit the scheduled rate sheet (platform admin)
Operation id: `update_rate_sheet_api_v1_admin_pricing_rates__rates_id__put`
Edit the draft in place: its instant may move later or earlier while it stays in the future, and
moving it alone is a real edit. Once the instant passes the sheet is frozen — settled usage rows point
at it — and this answers 409 `RATE_SHEET_IN_FORCE`.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `rates_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): RateSheetEditBody
- `rates` · object · required
- `effective_from` · string (date-time) · required
- `reason` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_RateSheetData_
- `request_id` · string · required
- `success` · true
- `data` · RateSheetData · required: A vendor rate table and when it takes effect. `status` is computed by payments against its own clock, so the console picks the live row off the list rather than comparing timestamps itself. A draft is rewritten in place, so `created_at` is when it was opened and `updated_at` when it was last written.
- `rates_id` · string (uuid) · required
- `name` · string | null
- `rates` · object · required
- `effective_from` · string (date-time) | null
- `status` · string · required
- `created_by` · string | null
- `reason` · string | null
- `created_at` · string (date-time) | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Promos
8 endpoints. HTML: https://developers.keystone.app/api/console/promos/
### GET /api/v1/admin/promos/coupons
List coupons (platform admin)
Operation id: `list_coupons_api_v1_admin_promos_coupons_get`
Every coupon, newest first. `include_deleted` adds the ones already deleted at Stripe — their
rows are kept so past redemptions still resolve to the terms that were granted.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `include_deleted` | query | boolean | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CouponListData_
- `request_id` · string · required
- `success` · true
- `data` · CouponListData · required
- `coupons` · CouponData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/promos/coupons
Create a coupon (platform admin)
Operation id: `create_coupon_api_v1_admin_promos_coupons_post`
Create the discount's terms. The console names the plans it applies to (`applies_to_plan_names`,
from `GET /promos/plans`); payments resolves each to the active plan's Stripe Product, which is why
a name that is not an active priced plan comes back as a 400 rather than a silent unrestricted
coupon. The restriction is immutable afterwards — a different set of plans is a different coupon.
`idempotency_key` is the console's, generated once per form submission and passed to Stripe
verbatim, so a retry after a lost response returns the first coupon instead of minting a second.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CouponCreateBody
- `name` · string · required
- `percent_off` · string | null
- `amount_off` · integer | null
- `currency` · string | null
- `duration` · string · required
- `duration_in_months` · integer | null
- `applies_to_plan_names` · string[] | null
- `max_redemptions` · integer | null
- `redeem_by` · string (date-time) | null
- `idempotency_key` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_CouponData_
- `request_id` · string · required
- `success` · true
- `data` · CouponData · required: A coupon as the admin screen renders it. `applies_to_plan_names` is `None` for "every plan" and `[]` for "restricted to products that are not any active plan of ours" — which only happens for a coupon created in the Stripe Dashboard.
- `coupon_id` · string (uuid) · required
- `name` · string · required
- `percent_off` · string | null
- `amount_off` · integer | null
- `currency` · string | null
- `duration` · string · required
- `duration_in_months` · integer | null
- `applies_to_plan_names` · string[] | null
- `max_redemptions` · integer | null
- `redeem_by` · string (date-time) | null
- `valid` · boolean · required
- `deleted_at` · string (date-time) | null
- `created_by` · string | null
- `times_redeemed` · integer
- `created_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/promos/coupons/{coupon_id}
One coupon with its codes (platform admin)
Operation id: `get_coupon_api_v1_admin_promos_coupons__coupon_id__get`
The detail screen: the coupon plus its code strings, each with its allowlist. A targeted code is
issued once per business under the same string, so the entries **are** the allowlist; a public code
has a single entry with no business. Every entry's business is enriched with its `company_name`
here — payments stores only ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `coupon_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CouponDetailData_
- `request_id` · string · required
- `success` · true
- `data` · CouponDetailData · required
- `coupon_id` · string (uuid) · required
- `name` · string · required
- `percent_off` · string | null
- `amount_off` · integer | null
- `currency` · string | null
- `duration` · string · required
- `duration_in_months` · integer | null
- `applies_to_plan_names` · string[] | null
- `max_redemptions` · integer | null
- `redeem_by` · string (date-time) | null
- `valid` · boolean · required
- `deleted_at` · string (date-time) | null
- `created_by` · string | null
- `times_redeemed` · integer
- `created_at` · string (date-time) | null
- `codes` · CodeGroupData[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/promos/coupons/{coupon_id}
Delete a coupon (platform admin)
Operation id: `delete_coupon_api_v1_admin_promos_coupons__coupon_id__delete`
Delete at Stripe. New redemptions stop and every code under it goes inactive, but **discounts
already granted keep running to their duration** — Stripe copies the terms onto the subscription at
redemption, so deleting the coupon does not claw anything back. The mirror row survives, flagged
deleted, so past redemptions still name their terms.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `coupon_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CouponData_
- `request_id` · string · required
- `success` · true
- `data` · CouponData · required: A coupon as the admin screen renders it. `applies_to_plan_names` is `None` for "every plan" and `[]` for "restricted to products that are not any active plan of ours" — which only happens for a coupon created in the Stripe Dashboard.
- `coupon_id` · string (uuid) · required
- `name` · string · required
- `percent_off` · string | null
- `amount_off` · integer | null
- `currency` · string | null
- `duration` · string · required
- `duration_in_months` · integer | null
- `applies_to_plan_names` · string[] | null
- `max_redemptions` · integer | null
- `redeem_by` · string (date-time) | null
- `valid` · boolean · required
- `deleted_at` · string (date-time) | null
- `created_by` · string | null
- `times_redeemed` · integer
- `created_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/promos/coupons/{coupon_id}/promotion-codes
Issue a code, or add businesses to one (platform admin)
Operation id: `create_promotion_codes_api_v1_admin_promos_coupons__coupon_id__promotion_codes_post`
No `businesses` mints one public code; with them, one customer-restricted code **per business**
sharing the string — which is also how a business is added to an existing code's allowlist.
Unknown businesses are refused here, before payments is called: payments would create a Stripe
Customer for the id to restrict the code to, and a Customer minted for a typo is permanent. The
`company_name` sor holds is passed along to label that Customer, because payments has no other
source for it.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `coupon_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PromotionCodesCreateBody
- `code` · string · required
- `businesses` · BusinessRefBody[]
- `business_id` · string (uuid) · required
- `expires_at` · string (date-time) | null
- `max_redemptions` · integer | null
- `first_time_transaction` · boolean
- `minimum_amount` · integer
- `listed_for_auto_apply` · boolean | null
- `idempotency_key` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_PromotionCodesData_
- `request_id` · string · required
- `success` · true
- `data` · PromotionCodesData · required
- `promotion_codes` · PromotionCodeData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/promos/plans
Plans a coupon can be restricted to (platform admin)
Operation id: `list_promo_plans_api_v1_admin_promos_plans_get`
The plan picker on the coupon form. This is payments' catalogue **without** the free plan: a
plan with no Stripe price has no Product to carry a discount, so offering it would only produce a
400 on submit. The names here are exactly what `applies_to_plan_names` takes.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PromoPlansData_
- `request_id` · string · required
- `success` · true
- `data` · PromoPlansData · required
- `plans` · PromoPlanData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/promos/promotion-codes/{promotion_code_id}
Activate, deactivate, or list a promotion code (platform admin)
Operation id: `update_promotion_code_api_v1_admin_promos_promotion_codes__promotion_code_id__patch`
`active=false` is how a business leaves an allowlist — codes are never deleted at Stripe.
`listed_for_auto_apply` controls whether the catalogue advertises the code in the customer's promo
dropdown; an unlisted code still works when typed at Checkout.
Stripe refuses to reactivate a code that expired, hit its cap, or whose coupon was deleted, and
that refusal reaches the admin verbatim rather than as a generic failure.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `promotion_code_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): PromotionCodeUpdateBody
- `active` · boolean | null
- `listed_for_auto_apply` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_PromotionCodeData_
- `request_id` · string · required
- `success` · true
- `data` · PromotionCodeData · required
- `promotion_code_id` · string (uuid) · required
- `coupon_id` · string (uuid) · required
- `code` · string · required
- `active` · boolean
- `business_id` · string (uuid) | null
- `company_name` · string | null
- `expires_at` · string (date-time) | null
- `max_redemptions` · integer | null
- `first_time_transaction` · boolean
- `minimum_amount` · integer
- `listed_for_auto_apply` · boolean
- `created_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/promos/redemptions
Who redeemed what (platform admin)
Operation id: `list_redemptions_api_v1_admin_promos_redemptions_get`
Redemption reporting, newest first, filterable by business, coupon or code. A row is a Stripe
Discount as payments mirrors it, so `ends_at` is null while the discount is still running. Each
row's business is named here from sor's table.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | query | string (uuid) \| null | no | |
| `coupon_id` | query | string (uuid) \| null | no | |
| `promotion_code_id` | query | string (uuid) \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_RedemptionsData_
- `request_id` · string · required
- `success` · true
- `data` · RedemptionsData · required
- `redemptions` · RedemptionData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Reviews
5 endpoints. HTML: https://developers.keystone.app/api/console/reviews/
### GET /api/v1/admin/businesses/{business_id}/reviews
List reviews
Operation id: `list_reviews_api_v1_admin_businesses__business_id__reviews_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `cursor` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_AdminReview__
- `request_id` · string · required
- `success` · true
- `data` · AdminReview[] · required
- `id` · string · required
- `business_id` · string · required
- `location_id` · string | null
- `reviewer_name` · string · required
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer · required
- `content_markdown` · string | null
- `source` · string | null
- `source_url` · string | null
- `featured` · boolean
- `reviewed_at` · string (date-time) · required
- `photos` · PhotoData[]
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/reviews
Create review
Operation id: `create_review_api_v1_admin_businesses__business_id__reviews_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AdminReviewCreate
- `reviewer_name` · string · required
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer · required
- `content_markdown` · string | null
- `source` · string · required
- `source_url` · string | null
- `featured` · boolean
- `reviewed_at` · string (date-time) · required
- `location_id` · string | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_AdminReview_
- `request_id` · string · required
- `success` · true
- `data` · AdminReview · required: Admin review for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_id` · string | null
- `reviewer_name` · string · required
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer · required
- `content_markdown` · string | null
- `source` · string | null
- `source_url` · string | null
- `featured` · boolean
- `reviewed_at` · string (date-time) · required
- `photos` · PhotoData[]
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/reviews/{review_id}
Get review details
Operation id: `get_review_api_v1_admin_businesses__business_id__reviews__review_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_AdminReview_
- `request_id` · string · required
- `success` · true
- `data` · AdminReview · required: Admin review for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_id` · string | null
- `reviewer_name` · string · required
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer · required
- `content_markdown` · string | null
- `source` · string | null
- `source_url` · string | null
- `featured` · boolean
- `reviewed_at` · string (date-time) · required
- `photos` · PhotoData[]
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/reviews/{review_id}
Update review
Operation id: `update_review_api_v1_admin_businesses__business_id__reviews__review_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): AdminReviewUpdate
- `reviewer_name` · string | null
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer | null
- `content_markdown` · string | null
- `source` · string | null
- `source_url` · string | null
- `featured` · boolean | null
- `reviewed_at` · string (date-time) | null
- `location_id` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_AdminReview_
- `request_id` · string · required
- `success` · true
- `data` · AdminReview · required: Admin review for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_id` · string | null
- `reviewer_name` · string · required
- `reviewer_title` · string | null
- `company` · string | null
- `rating` · integer · required
- `content_markdown` · string | null
- `source` · string | null
- `source_url` · string | null
- `featured` · boolean
- `reviewed_at` · string (date-time) · required
- `photos` · PhotoData[]
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/reviews/{review_id}
Delete review
Operation id: `delete_review_api_v1_admin_businesses__business_id__reviews__review_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: SEO
6 endpoints. HTML: https://developers.keystone.app/api/console/seo/
### GET /api/v1/admin/businesses/{business_id}/seo/actions
The site's prioritized SEO action list
Operation id: `list_actions_api_v1_admin_businesses__business_id__seo_actions_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ActionsData_
- `request_id` · string · required
- `success` · true
- `data` · ActionsData · required
- `businessId` · string (uuid) · required
- `websiteId` · string (uuid) · required
- `actions` · ActionItem[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/seo/blog/overview
Blog overview: search totals across every post, tracked keywords, per-post views
Operation id: `get_blog_overview_api_v1_admin_businesses__business_id__seo_blog_overview_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BlogOverviewData_
- `request_id` · string · required
- `success` · true
- `data` · BlogOverviewData · required
- `businessId` · string (uuid) · required
- `websiteId` · string (uuid) · required
- `generatedAt` · string (date-time) · required
- `window` · Window · required
- `totals` · BlogTotals · required
- `keywords` · BlogKeywordRow[] · required
- `posts` · object · required
- `coverage` · Coverage · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/seo/blog/posts/{blog_post_id}
Per-post performance: traffic, search, audit, speed and quality, with 30-day changes
Operation id: `get_blog_post_performance_api_v1_admin_businesses__business_id__seo_blog_posts__blog_post_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `blog_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BlogPostPerformanceData_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostPerformanceData · required
- `blogPostId` · string (uuid) · required
- `businessId` · string (uuid) · required
- `websiteId` · string (uuid) · required
- `slug` · string · required
- `path` · string · required
- `publishedOn` · string (date) | null
- `generatedAt` · string (date-time) · required
- `window` · Window · required
- `isNew` · boolean · required
- `sections` · PerfSection[] · required
- `queries` · DrivingQuery[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/seo/scores
Current SEO scores per category, with history
Operation id: `get_scores_api_v1_admin_businesses__business_id__seo_scores_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ScoresData_
- `request_id` · string · required
- `success` · true
- `data` · ScoresData · required
- `businessId` · string (uuid) · required
- `websiteId` · string (uuid) · required
- `generatedAt` · string (date-time) · required
- `scores` · CategoryScore[] · required
- `history` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/seo/site-health
Site health: technical audit, content review, traffic and search, latest vs previous
Operation id: `get_site_health_api_v1_admin_businesses__business_id__seo_site_health_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SiteHealthData_
- `request_id` · string · required
- `success` · true
- `data` · SiteHealthData · required
- `businessId` · string (uuid) · required
- `websiteId` · string (uuid) · required
- `domain` · string · required
- `generatedAt` · string (date-time) · required
- `build` · BuildBlock | null
- `reads` · ReadsBlock | null
- `traffic` · TrafficBlock | null
- `conversions` · ConversionsBlock | null
- `search` · SearchBlock | null
- `issuesTrend` · IssueTrendPoint[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/seo/portfolio
Every site's current overall score — worst first
Operation id: `portfolio_api_v1_admin_seo_portfolio_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PortfolioData_
- `request_id` · string · required
- `success` · true
- `data` · PortfolioData · required
- `entries` · PortfolioEntry[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Service items
9 endpoints. HTML: https://developers.keystone.app/api/console/service-items/
### GET /api/v1/admin/businesses/{business_id}/service-items
Get all service items
Operation id: `list_service_items_api_v1_admin_businesses__business_id__service_items_get`
Get all service items for a business (ordered by display_order). Member services not included.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceItemData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/service-items
Create service item
Operation id: `create_service_item_api_v1_admin_businesses__business_id__service_items_post`
Create a new service item.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemCreateBody
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[]
- `is_featured` · boolean | null
**Responses**
- `201` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/service-items/{item_id}
Get service item details
Operation id: `get_service_item_api_v1_admin_businesses__business_id__service_items__item_id__get`
Get service item by id with member services. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/service-items/{item_id}
Update service item
Operation id: `update_service_item_api_v1_admin_businesses__business_id__service_items__item_id__put`
Update service item details. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemUpdateBody
- `item_name` · string | null
- `item_description` · string | null
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[] | null
- `is_featured` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/service-items/{item_id}
Delete service item
Operation id: `delete_service_item_api_v1_admin_businesses__business_id__service_items__item_id__delete`
Soft-delete a service item. Member services survive (join rows cascade). 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/service-items/{item_id}/services
Set service item member services
Operation id: `set_item_services_api_v1_admin_businesses__business_id__service_items__item_id__services_put`
Replace the service item's member services (and their in-item order). 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemServicesBody
- `ordered_service_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/service-items/{item_id}/services/{service_id}
Add service to service item
Operation id: `add_service_to_item_api_v1_admin_businesses__business_id__service_items__item_id__services__service_id__post`
Add a service to a service item (appended last). 404 if item not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/service-items/{item_id}/services/{service_id}
Remove service from service item
Operation id: `remove_service_from_item_api_v1_admin_businesses__business_id__service_items__item_id__services__service_id__delete`
Remove a service from a service item. 404 if item or membership not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/service-items/reorder
Reorder service items
Operation id: `reorder_service_items_api_v1_admin_businesses__business_id__service_items_reorder_put`
Reorder service items by ordered list of item ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemReorderBody
- `ordered_item_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceItemData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Services
8 endpoints. HTML: https://developers.keystone.app/api/console/services/
### GET /api/v1/admin/businesses/{business_id}/services
Get all services
Operation id: `list_services_api_v1_admin_businesses__business_id__services_get`
Get all services for a business (ordered by display_order).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceData[] · required
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/services
Create service
Operation id: `create_service_api_v1_admin_businesses__business_id__services_post`
Create a new service.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceCreateBody
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `slug` · string | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_ServiceData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceData · required: Service for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/services/{service_id}
Get service details
Operation id: `get_service_api_v1_admin_businesses__business_id__services__service_id__get`
Get service by id. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ServiceData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceData · required: Service for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/services/{service_id}
Update service
Operation id: `update_service_api_v1_admin_businesses__business_id__services__service_id__put`
Update service details. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceUpdateBody
- `service_name` · string | null
- `service_description` · string | null
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `slug` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_ServiceData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceData · required: Service for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/services/{service_id}
Delete service
Operation id: `delete_service_api_v1_admin_businesses__business_id__services__service_id__delete`
Delete a service. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/services/{service_id}/service-items
List service items for a service
Operation id: `list_service_items_for_service_api_v1_admin_businesses__business_id__services__service_id__service_items_get`
Service items belonging to this service, ordered by per-service display_order.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceItemData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/services/{service_id}/service-items
Set service items for a service
Operation id: `set_service_items_for_service_api_v1_admin_businesses__business_id__services__service_id__service_items_put`
Replace the full set of service items for a service (ordered). Cross-tenant guard applied.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceServiceItemsBody
- `ordered_service_item_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceItemData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/services/reorder
Reorder services
Operation id: `reorder_services_api_v1_admin_businesses__business_id__services_reorder_put`
Reorder services by ordered list of service ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): ServiceReorderBody
- `ordered_service_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceData[] · required
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Social agent
2 endpoints. HTML: https://developers.keystone.app/api/console/social-agent/
### GET /api/v1/businesses/{business_id}/social-agent/config
Get effective social-agent config for a business
Operation id: `get_config_api_v1_businesses__business_id__social_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SocialAgentConfigData · required
- `business_id` · string · required
- `enabled` · boolean
- `auto_publish` · boolean
- `frequency_type` · string
- `instructions` · string
- `meta` · SocialAgentConfigMeta · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/social-agent/config
Update social-agent config for a business
Operation id: `patch_config_api_v1_businesses__business_id__social_agent_config_patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SocialAgentConfigPatchBody
- `enabled` · boolean | null
- `auto_publish` · boolean | null
- `frequency_type` · string | null
- `instructions` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SocialAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · SocialAgentConfigData · required
- `business_id` · string · required
- `enabled` · boolean
- `auto_publish` · boolean
- `frequency_type` · string
- `instructions` · string
- `meta` · SocialAgentConfigMeta · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Social integrations
11 endpoints. HTML: https://developers.keystone.app/api/console/social-integrations/
### GET /api/v1/admin/businesses/{business_id}/social_integrations/meta
Get Meta social integration status
Operation id: `get_meta_integration_api_v1_admin_businesses__business_id__social_integrations_meta_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialIntegrationStatusData_
- `request_id` · string · required
- `success` · true
- `data` · SocialIntegrationStatusData · required
- `business_id` · string · required
- `provider` · string
- `preferred_channel` · string
- `status` · string · required
- `channels` · SocialIntegrationChannelData[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/social_integrations/meta
Disconnect Meta social integration
Operation id: `disconnect_meta_integration_api_v1_admin_businesses__business_id__social_integrations_meta_delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `connection_channel` | query | string | yes | agency_business\|instagram_direct |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialIntegrationData_
- `request_id` · string · required
- `success` · true
- `data` · SocialIntegrationData · required
- `id` · string | null
- `business_id` · string · required
- `provider` · string
- `status` · string · required
- `scopes` · string[]
- `external_user_id` · string | null
- `external_user_name` · string | null
- `last_synced_at` · integer | null
- `last_error_code` · string | null
- `last_error_message` · string | null
- `created_at` · integer | null
- `updated_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/social_integrations/meta/agency
Get Keystone agency Meta connection readiness
Operation id: `get_meta_agency_status_api_v1_admin_businesses__business_id__social_integrations_meta_agency_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialAgencyConnectionData_
- `request_id` · string · required
- `success` · true
- `data` · SocialAgencyConnectionData · required
- `provider` · string
- `agency_business_id` · string | null
- `status` · string · required
- `connected` · boolean
- `business_onboarding_completed` · boolean
- `business_profiles_publishable` · boolean
- `publish_blocking_reason` · string | null
- `last_validated_at` · integer | null
- `last_error_code` · string | null
- `last_error_message` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_integrations/meta/agency/validate
Validate Keystone agency Meta connection
Operation id: `validate_meta_agency_api_v1_admin_businesses__business_id__social_integrations_meta_agency_validate_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialAgencyConnectionData_
- `request_id` · string · required
- `success` · true
- `data` · SocialAgencyConnectionData · required
- `provider` · string
- `agency_business_id` · string | null
- `status` · string · required
- `connected` · boolean
- `business_onboarding_completed` · boolean
- `business_profiles_publishable` · boolean
- `publish_blocking_reason` · string | null
- `last_validated_at` · integer | null
- `last_error_code` · string | null
- `last_error_message` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/social_integrations/meta/connect_url
Get Meta OAuth connection URL
Operation id: `get_meta_connect_url_api_v1_admin_businesses__business_id__social_integrations_meta_connect_url_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_MetaConnectUrlData_
- `request_id` · string · required
- `success` · true
- `data` · MetaConnectUrlData · required
- `connect_url` · string · required
- `expires_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/social_integrations/meta/instagram_direct/connect_url
Get Instagram direct OAuth connection URL
Operation id: `get_instagram_direct_connect_url_api_v1_admin_businesses__business_id__social_integrations_meta_instagram_direct_connect_url_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_MetaConnectUrlData_
- `request_id` · string · required
- `success` · true
- `data` · MetaConnectUrlData · required
- `connect_url` · string · required
- `expires_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/social_integrations/meta/instagram_direct/oauth/callback
Complete Instagram direct OAuth callback
Operation id: `instagram_direct_oauth_callback_api_v1_admin_businesses__business_id__social_integrations_meta_instagram_direct_oauth_callback_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `code` | query | string \| null | no | |
| `state` | query | string \| null | no | |
| `error` | query | string \| null | no | |
| `error_description` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/social_integrations/meta/oauth/callback
Complete Meta OAuth callback
Operation id: `meta_oauth_callback_api_v1_admin_businesses__business_id__social_integrations_meta_oauth_callback_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `code` | query | string \| null | no | |
| `state` | query | string \| null | no | |
| `error` | query | string \| null | no | |
| `error_description` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_integrations/meta/sync_profiles
Sync Meta Facebook and Instagram profiles
Operation id: `sync_meta_profiles_api_v1_admin_businesses__business_id__social_integrations_meta_sync_profiles_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `connection_channel` | query | string | no | all\|agency_business\|instagram_direct |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_MetaProfileSyncData_
- `request_id` · string · required
- `success` · true
- `data` · MetaProfileSyncData · required
- `connection` · SocialIntegrationData · required
- `profiles_synced` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/social_integrations/meta/instagram_direct/oauth/callback
Complete Instagram direct OAuth callback
Operation id: `instagram_direct_oauth_callback_static_api_v1_admin_social_integrations_meta_instagram_direct_oauth_callback_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `code` | query | string \| null | no | |
| `state` | query | string \| null | no | |
| `error` | query | string \| null | no | |
| `error_description` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/social_integrations/meta/oauth/callback
Complete Meta OAuth callback
Operation id: `meta_oauth_callback_static_api_v1_admin_social_integrations_meta_oauth_callback_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `code` | query | string \| null | no | |
| `state` | query | string \| null | no | |
| `error` | query | string \| null | no | |
| `error_description` | query | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Social posts
13 endpoints. HTML: https://developers.keystone.app/api/console/social-posts/
### GET /api/v1/admin/businesses/{business_id}/social_posts
List social posts for a business
Operation id: `list_social_posts_api_v1_admin_businesses__business_id__social_posts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string | no | Status filter: all\|draft\|queued\|published\|failed |
| `platform` | query | string | no | Platform filter: all\|facebook\|instagram |
| `search` | query | string \| null | no | Search post content |
| `cursor` | query | string \| null | no | |
| `direction` | query | string | no | Pagination direction: next\|prev |
| `limit` | query | integer | no | |
| `from_date` | query | string \| null | no | Optional scheduled/published lower bound as YYYY-MM-DD |
| `to_date` | query | string \| null | no | Optional scheduled/published upper bound as YYYY-MM-DD |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_SocialPostListItemData__
- `request_id` · string · required
- `success` · true
- `data` · SocialPostListItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `content_preview` · string · required
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" · required
- `origin` · string
- `scheduled_at` · integer | null
- `timezone` · string | null
- `published_at` · integer | null
- `photo_ids` · string[]
- `media_status` · string | null
- `thumbnail_photo_id` · string | null
- `thumbnail_url` · string | null
- `primary_profile_name` · string | null
- `primary_profile_username` · string | null
- `platforms` · SocialPostPlatformSummary[]
- `engagement` · SocialEngagementData
- `last_engagement_synced_at` · integer | null
- `has_external_changes` · boolean
- `external_change_kind` · string | null
- `removed_externally` · boolean
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts
Create a social post
Operation id: `create_social_post_api_v1_admin_businesses__business_id__social_posts_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostWriteBody
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `target_profile_ids` · string[]
- `target_formats` · SocialPostTargetFormatBody[]
- `social_profile_id` · string · required
- `format` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
- `publish_mode` · "draft" | "publish_now" | "schedule"
- `scheduled_at` · integer | null
- `timezone` · string | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_SocialPostData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostData · required
- `id` · string · required
- `business_id` · string · required
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `media_status` · string | null
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" · required
- `origin` · string
- `scheduled_at` · integer | null
- `timezone` · string | null
- `published_at` · integer | null
- `kairos_schedule_id` · string | null
- `kairos_schedule_status` · string | null
- `engagement` · SocialEngagementData
- `last_engagement_synced_at` · integer | null
- `media` · SocialPostPreviewMediaData[]
- `targets` · SocialPostTargetData[]
- `available_profiles` · SocialProfileData[]
- `has_external_changes` · boolean
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/social_posts/{social_post_id}
Get a social post
Operation id: `get_social_post_api_v1_admin_businesses__business_id__social_posts__social_post_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostData · required
- `id` · string · required
- `business_id` · string · required
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `media_status` · string | null
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" · required
- `origin` · string
- `scheduled_at` · integer | null
- `timezone` · string | null
- `published_at` · integer | null
- `kairos_schedule_id` · string | null
- `kairos_schedule_status` · string | null
- `engagement` · SocialEngagementData
- `last_engagement_synced_at` · integer | null
- `media` · SocialPostPreviewMediaData[]
- `targets` · SocialPostTargetData[]
- `available_profiles` · SocialProfileData[]
- `has_external_changes` · boolean
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/social_posts/{social_post_id}
Update a social post
Operation id: `update_social_post_api_v1_admin_businesses__business_id__social_posts__social_post_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostWriteBody
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `target_profile_ids` · string[]
- `target_formats` · SocialPostTargetFormatBody[]
- `social_profile_id` · string · required
- `format` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
- `publish_mode` · "draft" | "publish_now" | "schedule"
- `scheduled_at` · integer | null
- `timezone` · string | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostData · required
- `id` · string · required
- `business_id` · string · required
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `media_status` · string | null
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" · required
- `origin` · string
- `scheduled_at` · integer | null
- `timezone` · string | null
- `published_at` · integer | null
- `kairos_schedule_id` · string | null
- `kairos_schedule_status` · string | null
- `engagement` · SocialEngagementData
- `last_engagement_synced_at` · integer | null
- `media` · SocialPostPreviewMediaData[]
- `targets` · SocialPostTargetData[]
- `available_profiles` · SocialProfileData[]
- `has_external_changes` · boolean
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/social_posts/{social_post_id}
Delete a social post
Operation id: `delete_social_post_api_v1_admin_businesses__business_id__social_posts__social_post_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts/{social_post_id}/preview
Preview a social post
Operation id: `preview_social_post_api_v1_admin_businesses__business_id__social_posts__social_post_id__preview_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostPreviewBody
- `surface` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostPreviewData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostPreviewData · required
- `surface` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
- `profile_name` · string · required
- `profile_username` · string | null
- `profile_avatar_url` · string | null
- `caption` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `media` · SocialPostPreviewMediaData[]
- `engagement` · SocialEngagementData
- `created_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts/{social_post_id}/publish
Publish a social post immediately
Operation id: `publish_social_post_api_v1_admin_businesses__business_id__social_posts__social_post_id__publish_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostPublishBody
- `target_profile_ids` · string[]
- `target_formats` · SocialPostTargetFormatBody[]
- `social_profile_id` · string · required
- `format` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostPublishResult_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostPublishResult · required
- `social_post` · SocialPostData · required
- `results` · SocialPostPublishTargetResult[] · required
- `success` · boolean · required
- `successful_count` · integer · required
- `failed_count` · integer · required
- `total_count` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts/{social_post_id}/sync
Sync one post from Meta now (targeted re-read — catches IG edits)
Operation id: `sync_social_post_api_v1_admin_businesses__business_id__social_posts__social_post_id__sync_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialFeedSyncData_
- `request_id` · string · required
- `success` · true
- `data` · SocialFeedSyncData · required: Result of a feed sync trigger (header / per-post / full). ``status`` reflects the trigger outcome (``ok`` / ``in_progress`` / ``not_due`` / ``no_profiles`` …). The console invalidates + refetches posts on ``ok``; ``detail`` carries per-scope counters for observability.
- `status` · string · required
- `detail` · object | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts/{social_post_id}/sync_engagement
Refresh engagement metrics for a published social post
Operation id: `sync_social_post_engagement_api_v1_admin_businesses__business_id__social_posts__social_post_id__sync_engagement_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialEngagementSyncData_
- `request_id` · string · required
- `success` · true
- `data` · SocialEngagementSyncData · required
- `social_post` · SocialPostData · required
- `synced` · integer · required
- `failed` · integer · required
- `skipped` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/social_posts/calendar_events
List social post calendar events
Operation id: `list_social_post_calendar_events_api_v1_admin_businesses__business_id__social_posts_calendar_events_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `from_date` | query | string \| null | no | Optional lower bound as YYYY-MM-DD |
| `to_date` | query | string \| null | no | Optional upper bound as YYYY-MM-DD |
| `platform` | query | string | no | Platform filter: all\|facebook\|instagram |
| `status` | query | string | no | Status filter: all\|queued\|published\|failed |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_SocialPostCalendarEventData__
- `request_id` · string · required
- `success` · true
- `data` · SocialPostCalendarEventData[] · required
- `id` · string · required
- `post_id` · string · required
- `target_id` · string · required
- `social_post_id` · string · required
- `social_post_target_id` · string · required
- `social_profile_id` · string · required
- `platform` · "facebook" | "instagram" · required
- `status` · "pending" | "publishing" | "published" | "failed" | "draft" | "queued" | "publishing" | "published" | "failed" · required
- `scheduled_at` · integer | null
- `published_at` · integer | null
- `title` · string · required
- `content_preview` · string · required
- `profile_name` · string | null
- `profile_username` · string | null
- `thumbnail_photo_id` · string | null
- `thumbnail_url` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts/full_sync
Admin: deep full sync of all feeds (re-reads every post, catches edits)
Operation id: `full_sync_social_feeds_api_v1_admin_businesses__business_id__social_posts_full_sync_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialFeedSyncData_
- `request_id` · string · required
- `success` · true
- `data` · SocialFeedSyncData · required: Result of a feed sync trigger (header / per-post / full). ``status`` reflects the trigger outcome (``ok`` / ``in_progress`` / ``not_due`` / ``no_profiles`` …). The console invalidates + refetches posts on ``ok``; ``detail`` carries per-scope counters for observability.
- `status` · string · required
- `detail` · object | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts/generate
Generate editable social post content
Operation id: `generate_social_post_api_v1_admin_businesses__business_id__social_posts_generate_post`
Generate a caption -- through the workflow when it is enabled.
The workflow path persists the draft (and its photos) and returns it, so the
console navigates into an existing post. The inline path returns a caption
and nothing else, which is what it has always done.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string \| null | no | |
| `X-Workflow-Bypass` | header | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostGenerateBody
- `prompt` · string | null
- `target_profile_ids` · string[]
- `platforms` · ("facebook" | "instagram")[]
- `provider` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostGenerateData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostGenerateData · required
- `content_markdown` · string · required
- `media_briefs` · object[]: Photo briefs for the workflow worker to resolve against the media library, in display order. Empty unless social_media_briefs_enabled is on. The inline (non-workflow) callers return them but never act on them.
- `metadata` · SocialPostGenerateMetadata · required
- `generation_status` · string
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `social_post` · SocialPostData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_posts/sync
Sync all connected feeds now (incremental delta from Meta)
Operation id: `sync_social_feeds_api_v1_admin_businesses__business_id__social_posts_sync_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialFeedSyncData_
- `request_id` · string · required
- `success` · true
- `data` · SocialFeedSyncData · required: Result of a feed sync trigger (header / per-post / full). ``status`` reflects the trigger outcome (``ok`` / ``in_progress`` / ``not_due`` / ``no_profiles`` …). The console invalidates + refetches posts on ``ok``; ``detail`` carries per-scope counters for observability.
- `status` · string · required
- `detail` · object | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Social profiles
3 endpoints. HTML: https://developers.keystone.app/api/console/social-profiles/
### GET /api/v1/admin/businesses/{business_id}/social_profiles
List social profiles for a business
Operation id: `list_social_profiles_api_v1_admin_businesses__business_id__social_profiles_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `platform` | query | string | no | Platform filter: all\|facebook\|instagram |
| `active` | query | boolean \| null | no | Optional active profile filter |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_SocialProfileData__
- `request_id` · string · required
- `success` · true
- `data` · SocialProfileData[] · required
- `id` · string · required
- `business_id` · string · required
- `integration_connection_id` · string · required
- `platform` · "facebook" | "instagram" · required
- `profile_type` · "facebook_page" | "instagram_business_account" · required
- `connection_channel` · "agency_business" | "instagram_direct"
- `posting_identity` · "agency" | "business"
- `external_id` · string · required
- `parent_facebook_page_id` · string | null
- `direct_fallback_profile_id` · string | null
- `name` · string · required
- `username` · string | null
- `avatar_url` · string | null
- `external_permalink_url` · string | null
- `category` · string | null
- `display_name_override` · string | null
- `metadata` · object
- `agency_publish_ready` · boolean
- `token_expires_at` · integer | null
- `token_last_validated_at` · integer | null
- `token_last_refreshed_at` · integer | null
- `token_last_error_code` · string | null
- `token_last_error_message` · string | null
- `active` · boolean · required
- `sort_order` · integer · required
- `created_at` · integer · required
- `updated_at` · integer · required
- `display_name` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/admin/businesses/{business_id}/social_profiles/{profile_id}
Update local social profile settings
Operation id: `update_social_profile_api_v1_admin_businesses__business_id__social_profiles__profile_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `profile_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): SocialProfileUpdateBody
- `active` · boolean | null
- `sort_order` · integer | null
- `display_name_override` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SocialProfileData_
- `request_id` · string · required
- `success` · true
- `data` · SocialProfileData · required
- `id` · string · required
- `business_id` · string · required
- `integration_connection_id` · string · required
- `platform` · "facebook" | "instagram" · required
- `profile_type` · "facebook_page" | "instagram_business_account" · required
- `connection_channel` · "agency_business" | "instagram_direct"
- `posting_identity` · "agency" | "business"
- `external_id` · string · required
- `parent_facebook_page_id` · string | null
- `direct_fallback_profile_id` · string | null
- `name` · string · required
- `username` · string | null
- `avatar_url` · string | null
- `external_permalink_url` · string | null
- `category` · string | null
- `display_name_override` · string | null
- `metadata` · object
- `agency_publish_ready` · boolean
- `token_expires_at` · integer | null
- `token_last_validated_at` · integer | null
- `token_last_refreshed_at` · integer | null
- `token_last_error_code` · string | null
- `token_last_error_message` · string | null
- `active` · boolean · required
- `sort_order` · integer · required
- `created_at` · integer · required
- `updated_at` · integer · required
- `display_name` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/social_profiles/{profile_id}/refresh_token
Refresh a social profile publish token when supported
Operation id: `refresh_social_profile_token_api_v1_admin_businesses__business_id__social_profiles__profile_id__refresh_token_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `profile_id` | path | string (uuid) | yes | |
| `required_valid_until` | query | integer \| null | no | Optional epoch time the token must remain valid through |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialProfileTokenRefreshData_
- `request_id` · string · required
- `success` · true
- `data` · SocialProfileTokenRefreshData · required
- `social_profile_id` · string · required
- `platform` · "facebook" | "instagram" · required
- `connection_channel` · "agency_business" | "instagram_direct" · required
- `refresh_supported` · boolean · required
- `refreshed` · boolean
- `reconnect_required` · boolean
- `token_expires_at` · integer | null
- `last_refreshed_at` · integer | null
- `message` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Team
6 endpoints. HTML: https://developers.keystone.app/api/console/team/
### GET /api/v1/admin/businesses/{business_id}/team_members
Get all team members
Operation id: `list_team_members_api_v1_admin_businesses__business_id__team_members_get`
Get all team members for a business (ordered by display_order).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_TeamMemberData__
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData[] · required
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/team_members
Create team member
Operation id: `create_team_member_api_v1_admin_businesses__business_id__team_members_post`
Create a new team member.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): TeamMemberCreateBody
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_TeamMemberData_
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData · required: Team member for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/team_members/{team_member_id}
Get team member details
Operation id: `get_team_member_api_v1_admin_businesses__business_id__team_members__team_member_id__get`
Get team member by id. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `team_member_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_TeamMemberData_
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData · required: Team member for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/team_members/{team_member_id}
Update team member
Operation id: `update_team_member_api_v1_admin_businesses__business_id__team_members__team_member_id__put`
Update team member details. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `team_member_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): TeamMemberUpdateBody
- `full_name` · string | null
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_TeamMemberData_
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData · required: Team member for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/team_members/{team_member_id}
Delete team member
Operation id: `delete_team_member_api_v1_admin_businesses__business_id__team_members__team_member_id__delete`
Delete a team member. 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `team_member_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/team_members/reorder
Reorder team members
Operation id: `reorder_team_members_api_v1_admin_businesses__business_id__team_members_reorder_put`
Reorder team members by ordered list of team member ids.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): TeamMemberReorderBody
- `ordered_team_member_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_list_TeamMemberData__
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData[] · required
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Voice calls
2 endpoints. HTML: https://developers.keystone.app/api/console/voice-calls/
### GET /api/v1/admin/businesses/{business_id}/voice-calls/{call_id}
Front Desk call detail (transcript + analysis)
Operation id: `get_voice_call_api_v1_admin_businesses__business_id__voice_calls__call_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `call_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_VoiceCallDetailData_
- `request_id` · string · required
- `success` · true
- `data` · VoiceCallDetailData · required: One call's full record for the console thread widget (S3): the message row carries only the summary + ``ai_metadata.voice_call_id``; transcript and analysis are fetched on expand through this shape. No vendor URLs — playback streams through the recording proxy (design doc P28).
- `id` · string · required
- `business_id` · string · required
- `contact_id` · string | null
- `provider` · string · required
- `from_number` · string | null
- `to_number` · string | null
- `started_at` · string | null
- `ended_at` · string | null
- `duration_ms` · integer | null
- `disconnection_reason` · string | null
- `transcript` · string | null
- `summary` · string | null
- `captured_name` · string | null
- `captured_email` · string | null
- `custom_analysis` · object | null
- `has_recording` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/voice-calls/{call_id}/recording
Stream the call recording (audit-logged)
Operation id: `stream_voice_call_recording_api_v1_admin_businesses__business_id__voice_calls__call_id__recording_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `call_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Webchat
2 endpoints. HTML: https://developers.keystone.app/api/console/webchat/
### GET /api/v1/businesses/{business_id}/webchat/config
Get webchat config
Operation id: `get_webchat_config_api_v1_businesses__business_id__webchat_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/businesses/{business_id}/webchat/config
Update webchat greeting
Operation id: `put_webchat_config_api_v1_businesses__business_id__webchat_config_put`
Updates greeting only. Toggle chat on/off via PATCH /website-agent/config (webchat_enabled).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebchatConfigBody
- `greeting` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Website
19 endpoints. HTML: https://developers.keystone.app/api/console/website/
### GET /api/v1/admin/businesses/{business_id}/website
Get website for business
Operation id: `get_website_api_v1_admin_businesses__business_id__website_get`
Always 200. Returns optional website payload (null if not created).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteGetResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteGetResponse · required: Response for GET /businesses/{id}/website.
- `website` · WebsiteResponse | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website
Create website for business
Operation id: `create_website_api_v1_admin_businesses__business_id__website_post`
Create website + API key for the business, trigger provisioning, and return website only.
Takes no input beyond ``business_id``: the generator builds from the business's own
backend content, so there is no design prompt to supply.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsiteCreateBody | null
**Responses**
- `201` Successful Response: SuccessResponse_WebsiteCreateResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteCreateResponse · required: Response for create website endpoint: website only (API key is not returned).
- `website` · WebsiteResponse · required: Website fields for admin display.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website-check/artifacts/{artifact_id}
A screenshot the site agent's browser check took
Operation id: `get_website_check_artifact_api_v1_admin_businesses__business_id__website_check_artifacts__artifact_id__get`
The image bytes; 404 for a screenshot of another business, or none.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `artifact_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website-mod/requirements/{file_id}
Download a document attached for the site agent
Operation id: `download_website_mod_requirement_api_v1_admin_businesses__business_id__website_mod_requirements__file_id__get`
The document as the owner attached it; 404 for another business's.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `file_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/requirements/upload
Upload a document for the site agent (website editor chats)
Operation id: `upload_website_mod_requirements_api_v1_admin_businesses__business_id__website_mod_requirements_upload_post`
Store a document the owner attached for the site agent. With the
private bucket: a PDF, Word, Excel, PowerPoint or text file, validated and
converted at once (the answer lists the derived files and any
``warnings``); without it, text files only, as before.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (multipart/form-data, required): Body_upload_website_mod_requirements_api_v1_admin_businesses__business_id__website_mod_requirements_upload_post
- `file` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteAgentDocumentUpload_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteAgentDocumentUpload · required: The answer to a document upload for the site agent.
- `file_id` · string · required
- `filename` · string · required
- `content_type` · string | null
- `kind` · string | null
- `size` · integer | null
- `derived` · string[]
- `warnings` · string[]
- `pages` · integer | null
- `sheets` · integer | null
- `slides` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website/api-key
Reveal the website's public API key (live credential)
Operation id: `reveal_website_api_key_api_v1_admin_businesses__business_id__website_api_key_get`
Return the site's live public API key, in the clear.
404 while the feature flag is off; 404 for an unknown business or a
business with no website; 409 when the site has no readable key yet. The
key returned is the one the running site uses — revealing never rotates.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteApiKeyRevealData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteApiKeyRevealData · required: The site's live public API key, in the clear (reveal endpoint only). Deliberately ONE field. This is the only response body in sor that carries a working credential, so it carries nothing else: every extra field would be another value riding along in a payload that must not be stored, logged, or cached anywhere. ``key_prefix`` in particular is omitted — it is literally the key's own first eight characters, and a caller holding the whole key has no use for a slice of it. The value is the SAME string the site's ``.env`` carries (decrypted from ``business_api_keys.key_encrypted``); revealing never mints a new key, so a reveal cannot invalidate a running site.
- `api_key` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website/collaborators
List who has access to the business's site repo
Operation id: `list_website_collaborators_api_v1_admin_businesses__business_id__website_collaborators_get`
Direct collaborators (``status="active"``) merged with pending
invitations (``status="invited"``). Direct affiliation only: org-inherited
members (Keystone staff, the App) are asked out of the list at GitHub.
404 while the flag is off, for an unknown business, and for a business
with no site repo yet.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteCollaboratorsData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteCollaboratorsData · required: Combined view: active direct collaborators + pending invitations.
- `repo_full_name` · string · required
- `collaborators` · WebsiteCollaboratorRow[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website/collaborators
Invite a GitHub username to the business's site repo (push)
Operation id: `invite_website_collaborator_api_v1_admin_businesses__business_id__website_collaborators_post`
Invite at push. Idempotent-friendly: an already-invited or
already-active username answers 200 with ``already=true`` and a sentence
saying so — repeats are clean answers, not errors.
422 for a blank or unknown username; 503 ``GITHUB_APP_PERMISSION_MISSING``
when the App lacks the Administration permission.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteCollaboratorInviteBody
- `username` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteCollaboratorInviteData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteCollaboratorInviteData · required: Outcome of an invite — including the two idempotent "nothing to do" shapes, which are clean answers rather than errors: - fresh invitation: ``status="invited"``, ``already=False`` - already invited: ``status="invited"``, ``already=True`` (their pending invitation, untouched) - already active: ``status="active"``, ``already=True`` (GitHub answered 204: access exists or was granted directly, no invitation involved; ``permission`` is None because their actual level was not re-read)
- `username` · string · required
- `status` · "active" | "invited" · required
- `already` · boolean · required
- `permission` · "admin" | "maintain" | "push" | "triage" | "pull" | null
- `invitation_id` · integer | null
- `message` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/website/collaborators/{username}
Remove a collaborator or cancel their pending invitation
Operation id: `remove_website_collaborator_api_v1_admin_businesses__business_id__website_collaborators__username__delete`
One endpoint for both shapes of "take their access away": sor cancels
the pending invitation if that is what exists, removes the active direct
collaborator otherwise. 404 when the username has neither.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `username` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteCollaboratorRemoveData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteCollaboratorRemoveData · required: Outcome of a remove: which of the two GitHub acts sor performed — cancelling a pending invitation or removing an active collaborator.
- `username` · string · required
- `removed` · "invitation" | "collaborator" · required
- `message` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website/commits
List the website's change history (History tab)
Operation id: `list_website_commits_api_v1_admin_businesses__business_id__website_commits_get`
The conversation's changes as a git log, newest first.
404 while the preview flag is off, and for a business with no website row.
A website with no repository yet answers 200 with the honest-empty shape.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteCommitsResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteCommitsResponse · required: The History tab's read. A website row that has no repository yet answers honestly empty: ``repo_full_name`` and ``default_branch`` are None and ``commits`` is ``[]`` — the console shows "no repository yet". A GitHub failure, by contrast, is surfaced as an error, never as this empty shape. ``launched_at``/``confirm_text`` are the preview's confirm moment and the customer's confirming message — the console renders a "Building your full website…" narration row and a "Your site went live" divider at that point in the timeline. Both None for a site that never went through the preview flow (or has not confirmed yet).
- `repo_full_name` · string | null
- `default_branch` · string | null
- `launched_at` · string | null
- `confirm_text` · string | null
- `commits` · WebsiteCommitItem[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/admin/businesses/{business_id}/website/photos
Update website photo mappings
Operation id: `update_website_photos_api_v1_admin_businesses__business_id__website_photos_put`
Update website photo selections (partial). Only slot IDs present in the active config are applied.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsitePhotosUpdate
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteResponse · required: Website fields for admin display.
- `id` · string (uuid) · required
- `business_id` · string (uuid) · required
- `name` · string · required
- `slug` · string · required
- `domain` · string · required
- `preview_url` · string | null
- `worker_url` · string | null
- `custom_domain` · string | null
- `source_domain` · string | null
- `status` · string · required
- `custom_prompt` · string | null
- `provisioning_error` · string | null
- `provisioning_logs` · ProvisioningLogEntry[] | null
- `github_repo_id` · integer | null
- `website_photos` · WebsitePhotos | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/websites/{website_id}/custom-domain
Get custom domain state
Operation id: `get_custom_domain_api_v1_admin_websites__website_id__custom_domain_get`
Return current custom_domain, worker_name (repo_name), and preview_url when set; otherwise success with custom_domain: null.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CustomDomainGetResponse_
- `request_id` · string · required
- `success` · true
- `data` · CustomDomainGetResponse · required: Response for GET /websites/{website_id}/custom-domain. When custom_domain is set, includes worker_name and preview_url.
- `success` · boolean
- `custom_domain` · string | null
- `source_domain` · string | null
- `worker_name` · string | null
- `preview_url` · string | null
- `entri_enabled` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/websites/{website_id}/custom-domain
Create or attach custom domain
Operation id: `create_custom_domain_api_v1_admin_websites__website_id__custom_domain_post`
Set custom domain for a website. Normalizes apex to www, creates Cloudflare custom hostname if needed, returns DNS instructions.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CustomDomainCreateRequest
- `domain` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_CustomDomainCreateResponse_
- `request_id` · string · required
- `success` · true
- `data` · CustomDomainCreateResponse · required: Response for POST /websites/{website_id}/custom-domain.
- `success` · boolean
- `custom_domain` · string · required
- `dns_instructions` · DnsInstructions · required
- `converted_from` · string | null
- `message` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/websites/{website_id}/custom-domain
Remove custom domain
Operation id: `delete_custom_domain_api_v1_admin_websites__website_id__custom_domain_delete`
Remove custom domain: delete in Cloudflare and clear website.custom_domain. 404 if none configured.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CustomDomainDeleteResponse_
- `request_id` · string · required
- `success` · true
- `data` · CustomDomainDeleteResponse · required: Response for DELETE /websites/{website_id}/custom-domain.
- `message` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/websites/{website_id}/custom-domain-status
Get custom domain status
Operation id: `get_custom_domain_status_api_v1_admin_websites__website_id__custom_domain_status_get`
Return Cloudflare status, ssl_status, validation_records, and dns_instructions when custom_domain is set; else { status: 'none' }.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CustomDomainStatusResponse_
- `request_id` · string · required
- `success` · true
- `data` · CustomDomainStatusResponse · required: Response for GET /websites/{website_id}/custom-domain-status. When status is 'none', only success and status; else full CF details + dns_instructions.
- `success` · boolean
- `status` · string · required
- `domain` · string | null
- `ssl_status` · string | null
- `validation_errors` · any[] | null
- `validation_records` · object[] | null
- `dns_instructions` · DnsInstructions | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/websites/{website_id}/entri-setup
Get Entri setup (token + dnsRecords) for automated DNS
Operation id: `get_entri_setup_api_v1_admin_websites__website_id__entri_setup_get`
Mint a fresh Entri token and build the dnsRecords for entri.showEntri().
Dedicated endpoint (not folded into create/status) so a fresh short-lived token is minted
each time the user opens the modal. 409 if no custom domain is configured yet.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_EntriSetupResponse_
- `request_id` · string · required
- `success` · true
- `data` · EntriSetupResponse · required: Response for GET /websites/{website_id}/entri-setup: fresh token + records for entri.showEntri().
- `success` · boolean
- `application_id` · string · required
- `token` · string · required
- `prefilled_domain` · string · required
- `dns_records` · EntriDnsRecord[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/websites/{website_id}/provisioning-stream
Stream provisioning logs and status for a website (SSE)
Operation id: `stream_provisioning_api_v1_admin_websites__website_id__provisioning_stream_get`
Server-Sent Events stream of provisioning logs/status for a website.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `after_seq` | query | integer \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: string
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/websites/{website_id}/reconcile-custom-domain
Reconcile orphaned custom domain
Operation id: `reconcile_custom_domain_api_v1_admin_websites__website_id__reconcile_custom_domain_post`
Link an orphaned domain (in Cloudflare but not in DB) to this website. 404 if not in CF; 409 if linked to another website.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): CustomDomainCreateRequest
- `domain` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_CustomDomainReconcileResponse_
- `request_id` · string · required
- `success` · true
- `data` · CustomDomainReconcileResponse · required: Response for POST /websites/{website_id}/reconcile-custom-domain.
- `success` · boolean
- `message` · string · required
- `custom_domain` · string · required
- `cloudflare_status` · CloudflareStatusInfo · required: Cloudflare custom hostname status (status, ssl_status, validation_errors).
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/websites/{website_id}/reprovision
Re-provision a failed website
Operation id: `reprovision_website_api_v1_admin_websites__website_id__reprovision_post`
Reset a failed website and re-run its build in the background.
404 if the website does not exist, 409 if it is already active or currently
provisioning. On success the error is cleared and a fresh build is kicked off.
Without a website preview that is the classic path the create endpoint uses
(status reset to ``provisioning``); a website preview's failed full build is
retried as that build, seeded from the confirmed draft — 409 for a draft not
confirmed yet (see ``website_svc.reprovision_website``) — and the answer waits
(bounded) for the workflow start it makes, as the preview routes do.
A plain retry: it re-runs the same fixed brief against the business's current
backend content. The body is accepted but ignored (see ``WebsiteReprovisionBody``).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `website_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsiteReprovisionBody | null
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteReprovisionResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteReprovisionResponse · required: Response for reprovision endpoint: the website after its status is reset to provisioning.
- `website` · WebsiteResponse · required: Website fields for admin display.
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Website agent
3 endpoints. HTML: https://developers.keystone.app/api/console/website-agent/
### GET /api/v1/businesses/{business_id}/website-agent/config
Get effective website-agent settings for a business
Operation id: `get_config_api_v1_businesses__business_id__website_agent_config_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteAgentConfigData · required: Website agent + live webchat gate (webchat_enabled → webchat_configs).
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `webchat_enabled` · boolean
- `editor_model_id` · string | null
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/businesses/{business_id}/website-agent/config
Update website-agent settings for a business
Operation id: `patch_config_api_v1_businesses__business_id__website_agent_config_patch`
Partial diff. ``enabled`` / ``instructions`` persist as a new agent-settings version; ``webchat_enabled`` upserts webchat_configs in the same transaction (gates the live widget).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteAgentConfigPatchBody
- `enabled` · boolean | null
- `instructions` · string | null
- `webchat_enabled` · boolean | null
- `editor_model_id` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteAgentConfigData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteAgentConfigData · required: Website agent + live webchat gate (webchat_enabled → webchat_configs).
- `business_id` · string · required
- `enabled` · boolean · required
- `instructions` · string
- `webchat_enabled` · boolean
- `editor_model_id` · string | null
- `version` · integer | null
- `updated_at` · string (date-time) | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/businesses/{business_id}/website-agent/models
The models the website editor offers, and its defaults
Operation id: `list_models_api_v1_businesses__business_id__website_agent_models_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteAgentModelsData_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteAgentModelsData · required: The editor's model picker: what it offers, and what it starts on.
- `models` · WebsiteAgentModelOption[] · required
- `default_model_id` · string · required
- `business_default_model_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Website modifications
16 endpoints. HTML: https://developers.keystone.app/api/console/website-mod/
### GET /api/v1/admin/businesses/{business_id}/website-mod
Read the business's website-mod thread, pending change and preview
Operation id: `get_website_mod_state_api_v1_admin_businesses__business_id__website_mod_get`
The Changes tab's whole read, in one call.
``publishable`` answers the only question the tab's button needs: there is
a change awaiting approval, it belongs to this console thread, and both
the run id and the approval bundle ref needed to approve it are known.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModStateResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModStateResponse · required: The Changes tab's whole read: the chat, the pending change, the preview. ``messages`` is the control plane's thread-detail ``items`` passed straight through — sor does not reinterpret widget blocks, the console already maps them (it reads the same shape from the control plane on the inbox surface). ``preview_url`` is the version preview of ``preview_head_sha``, the newest commit on the modification branch; both are None until the branch's preview build lands a Cloudflare version.
- `thread_id` · string · required
- `website_id` · string · required
- `thread_status` · string | null
- `thread_failure_code` · string | null
- `thread_failure_message` · string | null
- `merge_conflict` · WebsiteModMergeConflict | null
- `messages` · object[]
- `pending_change` · WebsiteModPendingChange | null
- `approval_state` · string | null
- `approval_bundle_ref` · string | null
- `workflow_run_id` · string | null
- `preview_url` · string | null
- `preview_head_sha` · string | null
- `preview_frame_sha` · string | null
- `preview_kind` · string | null
- `session_enabled` · boolean
- `publishable` · boolean
- `progress` · WebsiteModProgress | null
- `pending_confirmation` · WebsiteModConfirmation | null
- `data_changed_at` · number | null
- `pending_data_changes` · string[]
- `capabilities` · EditorCapabilities
- `browser_check` · BrowserCheckView | null
- `versions` · WebsiteModVersion[]
- `undone_versions` · WebsiteModVersion[]
- `queued_requests` · EditorQueuedRequest[]
- `queue` · EditorQueueState | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/cancel
Cancel the pending website change
Operation id: `cancel_website_mod_change_api_v1_admin_businesses__business_id__website_mod_cancel_post`
Drive the run to completion, THEN clear pending, close the PR, unlock.
Order is the fix for a live bug: cleanup-first left the parked Temporal
workflow squatting on the thread's workflow id whenever the reject could
not be delivered (a change discarded mid-planning has no approval bundle
to reject), and the next submit — with every sor gate now green — hit the
engine's raw TEMPORAL_START_FAILED. The unwind now runs FIRST and fails
LOUDLY: on failure nothing has been cleared, the pending change is still
visible, and the cancel can simply be retried. Only once a completion
lever is armed (reject delivered, workflow already gone, or the run
tombstoned so the plan surface terminates it) does the local cleanup run —
idempotent with the engine's own reject-path cleanup.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsiteModCancelBody | null
- `comment` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModCancelResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModCancelResponse · required: Cancel result, straight from ``cancel_pending_website_mod``.
- `cancelled` · boolean · required
- `thread_id` · string · required
- `cleared_pending` · boolean · required
- `released_lock` · boolean · required
- `closed_pull_request_numbers` · integer[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/changes
Submit a website change request from the console
Operation id: `submit_website_mod_change_api_v1_admin_businesses__business_id__website_mod_changes_post`
202: the request is recorded on the thread and a run is started on it.
The run then drives the unchanged pipeline — plan-and-open-pr, preview
polling, the approval gate this tab's Publish answers, merge-deploy — so
the console never has to reimplement any stage of it. Poll the GET for the
pending change and the preview URL.
``text`` still rides along as the run's ``user_message`` input (the
workflow reads it); it is ALSO written to the thread, because an input is
not a record — before that, a reload lost the operator's own words.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteModChangeBody
- `text` · string | null · required
- `client_message_id` · string | null
- `model_id` · string | null
- `attachments` · WebsiteAgentAttachmentRef[]
- `kind` · "photo" | "document" · required
- `id` · string · required
- `delivery` · "now" | "queue" | null
**Responses**
- `202` Successful Response: SuccessResponse_WebsiteModChangeResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModChangeResponse · required: 202 body for a submitted change: what to poll, and where.
- `thread_id` · string · required
- `workflow_run_id` · string | null
- `status` · string · required
- `steered` · boolean
- `queued` · boolean
- `queued_request_id` · string | null
- `note_refused` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/check/override
Let a change its browser check holds back be published (Keystone staff)
Operation id: `override_website_mod_check_api_v1_admin_businesses__business_id__website_mod_check_override_post`
Keystone staff only: the reason and what the check had found are kept
in ``website_check_overrides``. 409 when nothing is held back.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BrowserCheckOverrideBody
- `reason` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_BrowserCheckView_
- `request_id` · string · required
- `success` · true
- `data` · BrowserCheckView · required: The newest browser check of what Publish (website-mod) or Finish (preview) would ship, and whether it holds that back.
- `id` · string · required
- `status` · string · required
- `blocked` · boolean · required
- `problems` · string[]
- `pages` · BrowserCheckPage[]
- `checked_at` · string (date-time) | null
- `overridden` · boolean
- `override_reason` · string | null
- `can_recheck` · boolean
- `rechecking` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/check/recheck
Look at the pending change's pages in a browser again
Operation id: `recheck_website_mod_check_api_v1_admin_businesses__business_id__website_mod_check_recheck_post`
202: the session pod looks again, without the site agent, at the pages
a check that never reached the browser was to look at. The state read
shows ``rechecking`` until the new check lands. 409 for a check a look
cannot settle (problems the agent saw), or while the agent works.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `202` Successful Response: SuccessResponse_BrowserCheckView_
- `request_id` · string · required
- `success` · true
- `data` · BrowserCheckView · required: The newest browser check of what Publish (website-mod) or Finish (preview) would ship, and whether it holds that back.
- `id` · string · required
- `status` · string · required
- `blocked` · boolean · required
- `problems` · string[]
- `pages` · BrowserCheckPage[]
- `checked_at` · string (date-time) | null
- `overridden` · boolean
- `override_reason` · string | null
- `can_recheck` · boolean
- `rechecking` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/confirmations/{confirmation_id}
Answer the site agent's question (continue or stop)
Operation id: `answer_website_mod_confirmation_api_v1_admin_businesses__business_id__website_mod_confirmations__confirmation_id__post`
202: the answer is on the thread and the parked run resumes with it.
Continue lets the agent carry out the plan it described; Stop reverts the
work so far. Either way the change goes back to ``planning`` until the
agent reports again.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `confirmation_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteModConfirmationBody
- `decision` · "continue" | "stop" · required
- `text` · string | null
- `client_message_id` · string | null
- `attachments` · WebsiteAgentAttachmentRef[]
- `kind` · "photo" | "document" · required
- `id` · string · required
**Responses**
- `202` Successful Response: SuccessResponse_WebsiteModChangeResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModChangeResponse · required: 202 body for a submitted change: what to poll, and where.
- `thread_id` · string · required
- `workflow_run_id` · string | null
- `status` · string · required
- `steered` · boolean
- `queued` · boolean
- `queued_request_id` · string | null
- `note_refused` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/merge-conflict/decline
Decline rebuilding a change the site's direct edits refused
Operation id: `decline_website_mod_rebuild_api_v1_admin_businesses__business_id__website_mod_merge_conflict_decline_post`
The owner's "no" to rebuilding a Publish the site's own, direct changes
refused: the change is let go for good, and the editor stops calling it a
failure (dev 2026-10-02: "no" did nothing, and the question came back).
Nothing is left to clean: the engine retired the change at the refused
merge (PR closed, pending cleared, lock released), and this read's own
retire catches any leftover. 409 when the thread's failed run noted no
such conflict.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModMergeConflict_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModMergeConflict · required: A Publish whose merge was refused because the site changed under the draft: what was changed directly, and the change to rebuild on top.
- `summary` · string · required
- `change` · string
- `rebuild_request` · string
- `declined` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website-mod/progress-stream
Stream website-mod agent progress events (SSE)
Operation id: `stream_website_mod_progress_api_v1_admin_businesses__business_id__website_mod_progress_stream_get`
Poll Redis progress for the in-flight plan and emit SSE until terminal.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `after_seq` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: string
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/publish
Publish the pending website change (approve + merge-deploy)
Operation id: `publish_website_mod_change_api_v1_admin_businesses__business_id__website_mod_publish_post`
202: the approval is recorded and the engine merges to main and deploys.
Publish IS the approval — one action, no separate approve step. It is
delivered as the control plane's approval decision rather than a direct
``merge_and_deploy`` because the run is parked on that approval signal: a
console-side merge would leave the workflow waiting forever on a PR that
no longer exists, and the deploy-completion signal it later receives would
never be read.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsiteModPublishBody | null
- `approval_bundle_ref` · string | null
- `comment` · string | null
**Responses**
- `202` Successful Response: SuccessResponse_WebsiteModPublishResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModPublishResponse · required: 202 body for publish: the approval was accepted, the deploy follows.
- `published` · boolean · required
- `thread_id` · string · required
- `workflow_run_id` · string · required
- `approval_bundle_ref` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/businesses/{business_id}/website-mod/queue/{item_id}
Take back a queued request before it goes
Operation id: `remove_queued_website_mod_change_api_v1_admin_businesses__business_id__website_mod_queue__item_id__delete`
Idempotent: a request no longer queued answers ``removed: false``. 409
``QUEUE_ITEM_SENDING`` for one already on its way.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_EditorQueueRemoveResponse_
- `request_id` · string · required
- `success` · true
- `data` · EditorQueueRemoveResponse · required: A queued request taken back: false when it was no longer queued.
- `removed` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website-mod/runs
The website's recent agent runs, each with its progress timeline
Operation id: `get_website_mod_runs_api_v1_admin_businesses__business_id__website_mod_runs_get`
What the agent did for each recent request — read back after a page
reload so the folded "What the agent did" account stays under the message
that asked for it. Two weeks of history, oldest first.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModRunsResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModRunsResponse · required
- `website_id` · string · required
- `runs` · WebsiteModRun[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website-mod/session
Read the website's live-preview session (pilot)
Operation id: `get_website_mod_session_api_v1_admin_businesses__business_id__website_mod_session_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModSessionResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModSessionResponse · required: The website's live-preview session (pilot): whether the session pod is serving this site, at which commit, and the URL the console frames.
- `website_id` · string · required
- `enabled` · boolean · required
- `status` · string | null
- `repo` · string | null
- `base_sha` · string | null
- `preview_url` · string | null
- `timings_s` · object
- `slot` · string | null
- `expires_at` · number | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/session/heartbeat
Keep the website's live-preview session leased while the editor is open
Operation id: `heartbeat_website_mod_session_api_v1_admin_businesses__business_id__website_mod_session_heartbeat_post`
The console calls this every minute or so while the site editor is
open. A slot whose lease nobody extends lapses (the pool's LEASE_TTL) and
the pod goes back to the warm floor — so tabs are viewers, and closing
the last one is what frees the slot, never a client-side beforeunload.
404 for a website not in session mode; a website holding no lease gets
the plain "enabled, nothing serving" response (pre-warm is what claims).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModSessionResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModSessionResponse · required: The website's live-preview session (pilot): whether the session pod is serving this site, at which commit, and the URL the console frames.
- `website_id` · string · required
- `enabled` · boolean · required
- `status` · string | null
- `repo` · string | null
- `base_sha` · string | null
- `preview_url` · string | null
- `timings_s` · object
- `slot` · string | null
- `expires_at` · number | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/session/prewarm
Pre-warm the website's live-preview session (pilot)
Operation id: `prewarm_website_mod_session_api_v1_admin_businesses__business_id__website_mod_session_prewarm_post`
Called by the console when the operator opens the site editor, so the
pod has cloned, installed and booted `next dev` before the first change
request arrives — the whole point of pre-warming. Idempotent: a pod already
serving this site at the right commit is left alone.
404 for a website not in session mode, so the console can fire this
unconditionally and simply learn that the pilot does not apply here.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModSessionResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModSessionResponse · required: The website's live-preview session (pilot): whether the session pod is serving this site, at which commit, and the URL the console frames.
- `website_id` · string · required
- `enabled` · boolean · required
- `status` · string | null
- `repo` · string | null
- `base_sha` · string | null
- `preview_url` · string | null
- `timings_s` · object
- `slot` · string | null
- `expires_at` · number | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/stop
Stop the change being made, keeping what the site agent has done
Operation id: `stop_website_mod_change_api_v1_admin_businesses__business_id__website_mod_stop_post`
The owner's Stop: the site agent's turn ends at its next step, and what
it changed so far becomes the change to review — publish it, ask for more,
or discard it. (Discard is the stop that throws the work away.) 409 when
this thread has no change being made, or its run cannot be reached, or
the run the Stop names (``plan_request_id``) is not the one being made:
a Stop that arrives late must not stop the queued request that followed.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsiteModStopBody | null
- `plan_request_id` · string | null
**Responses**
- `202` Successful Response: SuccessResponse_WebsiteEditorStopResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteEditorStopResponse · required: The owner's Stop on the change being made.
- `stopping` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-mod/undo
Take back the newest version of the change in review
Operation id: `undo_website_mod_change_api_v1_admin_businesses__business_id__website_mod_undo_post`
The owner's Undo: the change in review goes back to the version before
its newest (the branch, the preview, the summary), and the next request
builds on that. Only the newest version, while this thread's change waits
for review and is not being published; with the version that opened the
change left, Discard is the way. What it changed in the business records
stays.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteModUndoBody
- `version_id` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_WebsiteModUndoResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteModUndoResponse · required: The change in review is back on the version before the one taken back.
- `undone` · boolean · required
- `newest_version_id` · string · required
- `summary` · string
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Console API: Website preview
13 endpoints. HTML: https://developers.keystone.app/api/console/website-preview/
### GET /api/v1/admin/businesses/{business_id}/website-preview
Get the business's website preview, chat, and draft URL
Operation id: `get_website_preview_api_v1_admin_businesses__business_id__website_preview_get`
Preview + revisions[] (the chat) + the draft URL.
Every revision carries its OWN ``preview_url`` (the Worker version uploaded
for its checkpoint sha), and the top-level ``preview_url`` is the current
revision's — a new build means a NEW URL, so the console no longer has to
cache-bust a reused host, and past revisions stay viewable (History). Both
are None until the version lands, a minute or two after the revision goes
ready.
``live`` is the edit-pool slot serving this preview (live edits), which the
console frames instead while it is set; ``session_enabled`` whether this
preview's edits run live at all.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsitePreviewGetResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewGetResponse · required: Preview + the chat + the draft URL (the console's iframe source). ``preview_url`` is the CURRENT revision's version URL — the same value as that revision's own ``preview_url`` — so the console always frames a known build rather than "whatever was uploaded to the worker last". None while that revision's version has not appeared in Cloudflare yet; the console keeps showing the previous revision's URL until it does. ``live`` is the edit-pool slot serving this preview, when one is (the console frames it instead while set); ``session_enabled`` says whether this preview's edits run live at all (the console pre-warms and heartbeats only then). A revision's ``status`` may be ``no_change``: a reply, no new build.
- `preview` · WebsitePreviewData · required: Preview row for admin display (no ``brief_text`` — internal).
- `revisions` · WebsitePreviewRevisionData[] · required
- `preview_url` · string | null
- `live` · WebsitePreviewLive | null
- `session_enabled` · boolean
- `capabilities` · EditorCapabilities
- `browser_check` · BrowserCheckView | null
- `queue` · EditorQueueState | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-preview
Create (or start over) the business's website preview
Operation id: `create_website_preview_api_v1_admin_businesses__business_id__website_preview_post`
202: rows are committed and revision 0's generation is spawned; poll GET.
Also the "start over" path — an existing (unconfirmed, not in-flight)
preview is replaced with a fresh one on the same repo/worker, and the
onboarding website stage the replaced draft's failure left ``failed`` runs
again with the new one (``reopen_onboarding``: an owner's start-over, which
auto-onboarding's own re-entry is not).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsitePreviewCreateBody | null
- `user_prompt` · string | null
**Responses**
- `202` Successful Response: SuccessResponse_WebsitePreviewCreateResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewCreateResponse · required
- `preview` · WebsitePreviewData · required: Preview row for admin display (no ``brief_text`` — internal).
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website-preview/progress-stream
Stream the live preview edit's activity (SSE)
Operation id: `stream_website_preview_progress_api_v1_admin_businesses__business_id__website_preview_progress_stream_get`
The preview's live run as SSE, in website-mod's frame shape: its
events after ``after_seq``, ``reset`` when the run changes, ``done`` when
it ends. The console opens it when an edit is sent.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `after_seq` | query | integer | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/admin/businesses/{business_id}/website-preview/runs
The preview's past revisions, each with its activity trail
Operation id: `get_website_preview_runs_api_v1_admin_businesses__business_id__website_preview_runs_get`
What the agent did for each past chat turn, and for the full build once
it has run, so the account of the work survives a page reload and outlives
the stream (which only ever narrates the one live run).
Joined to the chat by ``revision``; the prompts, replies and timings come
from the preview read, which the console already has. ``build`` is the
launch's own account, filed under the confirm.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsitePreviewRunsResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewRunsResponse · required: Trails for the preview's recent revisions, oldest last, and the full build's once it has run. Revisions with no stored run are absent, and a run whose ring has aged out (two weeks) comes back with no events — both render as a turn without a trail.
- `preview_id` · string (uuid) · required
- `runs` · WebsitePreviewRun[]
- `build` · WebsitePreviewBuildRun | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-preview/session/heartbeat
Keep the preview's live edit session leased while the chat is open
Operation id: `heartbeat_website_preview_session_api_v1_admin_businesses__business_id__website_preview_session_heartbeat_post`
Every 60 s while the preview chat is open: a lease nobody extends lapses
and the slot returns to the warm floor. ``live: null`` when the preview
holds no lease (pre-warm is what claims). 404 when edits do not run live.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsitePreviewSessionResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewSessionResponse · required: Pre-warm / heartbeat answer: the read's two live-edit fields.
- `session_enabled` · boolean
- `live` · WebsitePreviewLive | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/businesses/{business_id}/website-preview/session/prewarm
Pre-warm the preview's live edit session
Operation id: `prewarm_website_preview_session_api_v1_admin_businesses__business_id__website_preview_session_prewarm_post`
Called once when the preview chat opens, so the slot has cloned,
installed and booted the draft before the first edit. Idempotent: a slot
already serving the current draft is left alone, and a run in flight keeps
what it serves. Nothing to warm (no ready draft yet, or the chat is closed)
answers ``live: null``. 404 when the preview's edits do not run live.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsitePreviewSessionResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewSessionResponse · required: Pre-warm / heartbeat answer: the read's two live-edit fields.
- `session_enabled` · boolean
- `live` · WebsitePreviewLive | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/website-previews/{preview_id}/check/override
Let a draft its browser check holds back be finished (Keystone staff)
Operation id: `override_website_preview_check_api_v1_admin_website_previews__preview_id__check_override_post`
Keystone staff only: the reason and what the check had found are kept
in ``website_check_overrides``. 409 when nothing is held back.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `preview_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): BrowserCheckOverrideBody
- `reason` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_BrowserCheckView_
- `request_id` · string · required
- `success` · true
- `data` · BrowserCheckView · required: The newest browser check of what Publish (website-mod) or Finish (preview) would ship, and whether it holds that back.
- `id` · string · required
- `status` · string · required
- `blocked` · boolean · required
- `problems` · string[]
- `pages` · BrowserCheckPage[]
- `checked_at` · string (date-time) | null
- `overridden` · boolean
- `override_reason` · string | null
- `can_recheck` · boolean
- `rechecking` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/website-previews/{preview_id}/check/recheck
Look at the draft's pages in a browser again
Operation id: `recheck_website_preview_check_api_v1_admin_website_previews__preview_id__check_recheck_post`
202: the edit slot looks again, without the site agent, at the pages a
check that never reached the browser was to look at. The read shows
``rechecking`` until the new check lands. 409 while a revision generates,
without live edits, or for a check a look cannot settle.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `preview_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `202` Successful Response: SuccessResponse_BrowserCheckView_
- `request_id` · string · required
- `success` · true
- `data` · BrowserCheckView · required: The newest browser check of what Publish (website-mod) or Finish (preview) would ship, and whether it holds that back.
- `id` · string · required
- `status` · string · required
- `blocked` · boolean · required
- `problems` · string[]
- `pages` · BrowserCheckPage[]
- `checked_at` · string (date-time) | null
- `overridden` · boolean
- `override_reason` · string | null
- `can_recheck` · boolean
- `rechecking` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/website-previews/{preview_id}/confirm
Confirm the website preview and start the full build
Operation id: `confirm_website_preview_api_v1_admin_website_previews__preview_id__confirm_post`
202: preview → confirmed, draft PR closed (never merged), seeded full
build fired after commit. Only a ready preview confirms (409 otherwise).
The body is optional and additive: ``{"text": ...}`` persists the
customer's confirming chat message on the preview as ``confirm_text`` (it
IS the confirmation, in the prototype's conversational shape); no body
keeps the original behaviour exactly.
With ``website_preview_engine_enabled``, a confirm while a revision is
generating answers 202 with ``queued: true`` instead of 409: it runs when
that revision settles ``ready``/``no_change``.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `preview_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsitePreviewConfirmBody | null
- `text` · string | null
**Responses**
- `202` Successful Response: SuccessResponse_WebsitePreviewConfirmResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewConfirmResponse · required: The confirmed preview — including ``confirm_text`` when the caller sent one (additive; a no-body confirm answers exactly as it did before). ``queued`` (engine mode): the confirm arrived while a revision was still generating, so nothing is confirmed yet — it runs when that revision settles ``ready``/``no_change``, and a failed settle clears it.
- `preview` · WebsitePreviewData · required: Preview row for admin display (no ``brief_text`` — internal).
- `queued` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/website-previews/{preview_id}/edits
Send a chat edit to the website preview
Operation id: `create_website_preview_edit_api_v1_admin_website_previews__preview_id__edits_post`
202 with revision n+1 spawned; 409 while any revision is generating.
The message's INTENT is decided server-side first (see
``website_preview.intent``): a message that reads as approval ("looks
good, ship it", "finish my website") runs the SAME confirm flow the
dedicated /confirm route runs — the service function is reused, never
duplicated — and answers with ``intent: "confirm"`` and no revision. Any
other message runs the edit flow exactly as before, with
``intent: "edit"``. The /confirm route itself is untouched (the console
chip may still call it directly).
A confirm-labelled message the preview cannot confirm (409: not ready,
nothing built yet) FALLS THROUGH to the edit flow instead of erroring:
the label was the classifier's guess, and dropping the customer's words
on the floor because the guess outran the preview's state was a real
lost-message bug. The dedicated /confirm route keeps its honest 409 —
there the caller SAID confirm.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `preview_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): WebsitePreviewEditBody
- `text` · string | null · required
- `model_id` · string | null
- `attachments` · WebsiteAgentAttachmentRef[]
- `kind` · "photo" | "document" · required
- `id` · string · required
- `client_message_id` · string | null
- `delivery` · "now" | "queue" | null
**Responses**
- `202` Successful Response: SuccessResponse_WebsitePreviewEditResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewEditResponse · required: 202 body for a chat message on the preview. ``intent`` says which flow the server ran for the message: ``edit`` (a new revision was spawned — ``revision`` carries it, exactly as before) or ``confirm`` (the message read as approval, so the CONFIRM flow ran — the preview is confirmed, the full build is armed, and ``revision`` is None because confirm never creates a revision row). Additive: pre-intent consumers of the edit path see the same shape they always did.
- `preview` · WebsitePreviewData · required: Preview row for admin display (no ``brief_text`` — internal).
- `revision` · WebsitePreviewRevisionData | null
- `intent` · string
- `queued` · boolean
- `steered` · boolean
- `queued_request_id` · string | null
- `note_refused` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/admin/website-previews/{preview_id}/queue/{item_id}
Take back a queued draft edit before it goes
Operation id: `remove_queued_website_preview_edit_api_v1_admin_website_previews__preview_id__queue__item_id__delete`
Idempotent: a request no longer queued answers ``removed: false``.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `preview_id` | path | string (uuid) | yes | |
| `item_id` | path | string | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_EditorQueueRemoveResponse_
- `request_id` · string · required
- `success` · true
- `data` · EditorQueueRemoveResponse · required: A queued request taken back: false when it was no longer queued.
- `removed` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/website-previews/{preview_id}/stop
Stop the draft edit being made, keeping what the site agent has done
Operation id: `stop_website_preview_edit_api_v1_admin_website_previews__preview_id__stop_post`
The owner's Stop: the site agent's turn ends at its next step, and what
it changed so far settles as the revision (nothing changed: no change).
409 when no edit is being made on a session pod (the job cannot be
stopped) or the pod cannot be reached; an edit still on its way to its
pod is stopped once it gets there. ``revision`` names the edit the Stop is
for: a late Stop, once that edit settled, never stops the queued one.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `preview_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, optional): WebsitePreviewStopBody | null
- `revision` · integer | null
**Responses**
- `202` Successful Response: SuccessResponse_WebsiteEditorStopResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsiteEditorStopResponse · required: The owner's Stop on the change being made.
- `stopping` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/admin/website-previews/{preview_id}/undo
Take back the draft's newest edit
Operation id: `undo_website_preview_edit_api_v1_admin_website_previews__preview_id__undo_post`
The owner's Undo: the draft goes back to the edit before the current
one (the first draft at the earliest), and the next edit builds on it.
409 while an edit is being made or a finish is queued, and with nothing
to undo.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `preview_id` | path | string (uuid) | yes | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsitePreviewUndoResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewUndoResponse · required: The draft is back on the edit before the one taken back.
- `preview` · WebsitePreviewData · required: Preview row for admin display (no ``brief_text`` — internal).
- `undone` · WebsitePreviewRevisionData · required: One revision — a chat turn: the prompt that caused it plus its outcome. ``trigger`` ``initial``/``chat`` rows are generation runs; a ``confirm`` row is the customer's closing message and carries no run (status ``confirmed``, no ``checkpoint_sha``, no ``run_id``).
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
# Agent API
Per-business CRUD the platform's agents use to write business information.
Service-to-service endpoints the Keystone agents (website, content, social, listings and the rest) call to read and write a business's records. Each resource mirrors a Business Info section.
- Audience: Keystone services
- Base URL: https://sor.keystone.app
- Authentication: Internal service key, sent as the `X-Internal-Api-Key` header.
- Endpoints: 46 in 8 groups
- HTML: https://developers.keystone.app/api/agent/
## Agent API: Business
2 endpoints. HTML: https://developers.keystone.app/api/agent/business/
### GET /api/v1/agent/businesses/{business_id}
Get business details (agent)
Operation id: `get_business_api_v1_agent_businesses__business_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BusinessProfileData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessProfileData · required: Business profile for GET response.
- `id` · string · required
- `company_name` · string | null
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `descriptions` · Descriptions | null
- `contact_info` · ContactInfo | null
- `social_profiles` · SocialProfiles | null
- `industries` · BusinessIndustries | null
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean
- `timezone` · string
- `timezone_source` · string
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}
Update business profile (agent)
Operation id: `patch_business_api_v1_agent_businesses__business_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): BusinessUpdateBody
- `company_name` · string | null
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `descriptions` · Descriptions | null
- `company_description` · string | null
- `about` · string | null
- `mission_statement` · string | null
- `values` · string[]
- `contact_info` · ContactInfo | null
- `primary_phone` · string | null
- `primary_email` · string | null
- `support_email` · string | null
- `sales_email` · string | null
- `external_management_url` · string | null
- `social_profiles` · SocialProfiles | null
- `facebook` · string | null
- `instagram` · string | null
- `twitter` · string | null
- `linkedin` · string | null
- `youtube` · string | null
- `tiktok` · string | null
- `google_my_business` · string | null
- `pinterest` · string | null
- `yelp` · string | null
- `tripadvisor` · string | null
- `google_review` · string | null
- `industries` · BusinessIndustries | null
- `primary` · string | null
- `secondary` · string[]
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean | null
- `timezone` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_BusinessProfileData_
- `request_id` · string · required
- `success` · true
- `data` · BusinessProfileData · required: Business profile for GET response.
- `id` · string · required
- `company_name` · string | null
- `year_founded` · integer | null
- `website_url` · string | null
- `tagline` · string | null
- `address` · Address | null
- `descriptions` · Descriptions | null
- `contact_info` · ContactInfo | null
- `social_profiles` · SocialProfiles | null
- `industries` · BusinessIndustries | null
- `terms_of_service_markdown` · string | null
- `privacy_policy_markdown` · string | null
- `is_active` · boolean
- `timezone` · string
- `timezone_source` · string
- `created_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Agent API: FAQ
6 endpoints. HTML: https://developers.keystone.app/api/agent/faq/
### GET /api/v1/agent/businesses/{business_id}/faqs
List FAQs (agent)
Operation id: `list_faqs_api_v1_agent_businesses__business_id__faqs_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_FaqData__
- `request_id` · string · required
- `success` · true
- `data` · FaqData[] · required
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/agent/businesses/{business_id}/faqs
Create FAQ (agent)
Operation id: `create_faq_api_v1_agent_businesses__business_id__faqs_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): FaqCreateBody
- `question` · string · required
- `answer` · string · required
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_FaqData_
- `request_id` · string · required
- `success` · true
- `data` · FaqData · required: FAQ for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/agent/businesses/{business_id}/faqs/{faq_id}
Get FAQ (agent)
Operation id: `get_faq_api_v1_agent_businesses__business_id__faqs__faq_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `faq_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FaqData_
- `request_id` · string · required
- `success` · true
- `data` · FaqData · required: FAQ for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/faqs/{faq_id}
Update FAQ (agent)
Operation id: `update_faq_api_v1_agent_businesses__business_id__faqs__faq_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `faq_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): FaqUpdateBody
- `question` · string | null
- `answer` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_FaqData_
- `request_id` · string · required
- `success` · true
- `data` · FaqData · required: FAQ for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}/faqs/{faq_id}
Partial update FAQ (agent)
Operation id: `patch_faq_api_v1_agent_businesses__business_id__faqs__faq_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `faq_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): FaqUpdateBody
- `question` · string | null
- `answer` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_FaqData_
- `request_id` · string · required
- `success` · true
- `data` · FaqData · required: FAQ for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `question` · string · required
- `answer` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/agent/businesses/{business_id}/faqs/{faq_id}
Delete FAQ (agent)
Operation id: `delete_faq_api_v1_agent_businesses__business_id__faqs__faq_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `faq_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Agent API: Jobs
6 endpoints. HTML: https://developers.keystone.app/api/agent/jobs/
### GET /api/v1/agent/businesses/{business_id}/job_postings
List job postings (agent)
Operation id: `list_job_postings_api_v1_agent_businesses__business_id__job_postings_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_JobPostingData__
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData[] · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/agent/businesses/{business_id}/job_postings
Create job posting (agent)
Operation id: `create_job_posting_api_v1_agent_businesses__business_id__job_postings_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): JobPostingCreateBody
- `title` · string · required
- `slug` · string | null
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus: Job posting status enum.
- `featured` · boolean
- `display_order` · integer | null
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_JobPostingData_
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData · required: Job posting for admin list/GET response.
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/agent/businesses/{business_id}/job_postings/{job_posting_id}
Get job posting (agent)
Operation id: `get_job_posting_api_v1_agent_businesses__business_id__job_postings__job_posting_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_JobPostingData_
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData · required: Job posting for admin list/GET response.
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/job_postings/{job_posting_id}
Update job posting (agent)
Operation id: `update_job_posting_api_v1_agent_businesses__business_id__job_postings__job_posting_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): JobPostingUpdateBody
- `title` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType | null
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus | null
- `featured` · boolean | null
- `display_order` · integer | null
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_JobPostingData_
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData · required: Job posting for admin list/GET response.
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}/job_postings/{job_posting_id}
Partial update job posting (agent)
Operation id: `patch_job_posting_api_v1_agent_businesses__business_id__job_postings__job_posting_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): JobPostingUpdateBody
- `title` · string | null
- `slug` · string | null
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType | null
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus | null
- `featured` · boolean | null
- `display_order` · integer | null
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_JobPostingData_
- `request_id` · string · required
- `success` · true
- `data` · JobPostingData · required: Job posting for admin list/GET response.
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `description_markdown` · string | null
- `requirements_markdown` · string | null
- `responsibilities_markdown` · string | null
- `benefits_markdown` · string | null
- `employment_type` · JobEmploymentType · required: Employment type enum for job postings.
- `experience_level` · string | null
- `location` · string | null
- `salary_range` · string | null
- `status` · JobStatus · required: Job posting status enum.
- `featured` · boolean
- `display_order` · integer
- `posted_at` · string (date-time) | null
- `expires_at` · string (date-time) | null
- `photo_ids` · string[]
- `photos` · PhotoData[]
- `applications_count` · integer
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/agent/businesses/{business_id}/job_postings/{job_posting_id}
Delete job posting (agent)
Operation id: `delete_job_posting_api_v1_agent_businesses__business_id__job_postings__job_posting_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `job_posting_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Agent API: Locations
6 endpoints. HTML: https://developers.keystone.app/api/agent/locations/
### GET /api/v1/agent/businesses/{business_id}/locations
List locations (agent)
Operation id: `list_locations_api_v1_agent_businesses__business_id__locations_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_LocationData__
- `request_id` · string · required
- `success` · true
- `data` · LocationData[] · required
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/agent/businesses/{business_id}/locations
Create location (agent)
Operation id: `create_location_api_v1_agent_businesses__business_id__locations_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): LocationCreateBody
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `monday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `tuesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `wednesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `thursday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `friday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `saturday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `sunday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `slug` · string | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_LocationData_
- `request_id` · string · required
- `success` · true
- `data` · LocationData · required: Location for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/agent/businesses/{business_id}/locations/{location_id}
Get location (agent)
Operation id: `get_location_api_v1_agent_businesses__business_id__locations__location_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `location_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_LocationData_
- `request_id` · string · required
- `success` · true
- `data` · LocationData · required: Location for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/locations/{location_id}
Update location (agent)
Operation id: `update_location_api_v1_agent_businesses__business_id__locations__location_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `location_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): LocationUpdateBody
- `location_name` · string | null
- `is_primary_location` · boolean | null
- `is_active_location` · boolean | null
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `monday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `tuesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `wednesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `thursday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `friday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `saturday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `sunday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `slug` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_LocationData_
- `request_id` · string · required
- `success` · true
- `data` · LocationData · required: Location for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}/locations/{location_id}
Partial update location (agent)
Operation id: `patch_location_api_v1_agent_businesses__business_id__locations__location_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `location_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): LocationUpdateBody
- `location_name` · string | null
- `is_primary_location` · boolean | null
- `is_active_location` · boolean | null
- `address` · Address | null
- `line1` · string | null
- `line2` · string | null
- `city` · string | null
- `state` · string | null
- `zip_code` · string | null
- `country` · string | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `monday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `tuesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `wednesday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `thursday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `friday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `saturday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `sunday` · DayHours | null
- `is_open` · boolean
- `open_time` · string | null
- `close_time` · string | null
- `slug` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_LocationData_
- `request_id` · string · required
- `success` · true
- `data` · LocationData · required: Location for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `location_name` · string · required
- `is_primary_location` · boolean
- `is_active_location` · boolean
- `address` · Address | null
- `phone` · string | null
- `email` · string | null
- `description` · string | null
- `timezone` · string | null
- `business_hours` · BusinessHours | null
- `slug` · string · required
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/agent/businesses/{business_id}/locations/{location_id}
Delete location (agent)
Operation id: `delete_location_api_v1_agent_businesses__business_id__locations__location_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `location_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Agent API: Packages
7 endpoints. HTML: https://developers.keystone.app/api/agent/packages/
### GET /api/v1/agent/businesses/{business_id}/packages
List packages (agent)
Operation id: `list_packages_api_v1_agent_businesses__business_id__packages_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_PackageData__
- `request_id` · string · required
- `success` · true
- `data` · PackageData[] · required
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/agent/businesses/{business_id}/packages
Create package (agent)
Operation id: `create_package_api_v1_agent_businesses__business_id__packages_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PackageCreateBody
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[]
- `is_featured` · boolean | null
**Responses**
- `201` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/agent/businesses/{business_id}/packages/{package_id}
Get package (agent)
Operation id: `get_package_api_v1_agent_businesses__business_id__packages__package_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/packages/{package_id}
Update package (agent)
Operation id: `update_package_api_v1_agent_businesses__business_id__packages__package_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PackageUpdateBody
- `package_name` · string | null
- `package_description` · string | null
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[] | null
- `is_featured` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}/packages/{package_id}
Partial update package (agent)
Operation id: `patch_package_api_v1_agent_businesses__business_id__packages__package_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PackageUpdateBody
- `package_name` · string | null
- `package_description` · string | null
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[] | null
- `is_featured` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/agent/businesses/{business_id}/packages/{package_id}
Delete package (agent)
Operation id: `delete_package_api_v1_agent_businesses__business_id__packages__package_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/packages/{package_id}/service-items
Set package member service items (agent)
Operation id: `set_package_service_items_api_v1_agent_businesses__business_id__packages__package_id__service_items_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `package_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PackageServiceItemsBody
- `ordered_service_item_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_PackageData_
- `request_id` · string · required
- `success` · true
- `data` · PackageData · required: Package for list/GET response. `service_items` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-package member queries.
- `id` · string · required
- `business_id` · string · required
- `package_name` · string · required
- `package_description` · string · required
- `package_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Agent API: Service items
7 endpoints. HTML: https://developers.keystone.app/api/agent/service-items/
### GET /api/v1/agent/businesses/{business_id}/service-items
List service items (agent)
Operation id: `list_service_items_api_v1_agent_businesses__business_id__service_items_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceItemData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/agent/businesses/{business_id}/service-items
Create service item (agent)
Operation id: `create_service_item_api_v1_agent_businesses__business_id__service_items_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemCreateBody
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[]
- `is_featured` · boolean | null
**Responses**
- `201` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/agent/businesses/{business_id}/service-items/{item_id}
Get service item (agent)
Operation id: `get_service_item_api_v1_agent_businesses__business_id__service_items__item_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/service-items/{item_id}
Update service item (agent)
Operation id: `update_service_item_api_v1_agent_businesses__business_id__service_items__item_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemUpdateBody
- `item_name` · string | null
- `item_description` · string | null
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[] | null
- `is_featured` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}/service-items/{item_id}
Partial update service item (agent)
Operation id: `patch_service_item_api_v1_agent_businesses__business_id__service_items__item_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemUpdateBody
- `item_name` · string | null
- `item_description` · string | null
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · number | string | null
- `slug` · string | null
- `photo_ids` · string[] | null
- `is_featured` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/agent/businesses/{business_id}/service-items/{item_id}
Delete service item (agent)
Operation id: `delete_service_item_api_v1_agent_businesses__business_id__service_items__item_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/service-items/{item_id}/services
Set service item member services (agent)
Operation id: `set_item_services_api_v1_agent_businesses__business_id__service_items__item_id__services_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `item_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ServiceItemServicesBody
- `ordered_service_ids` · string[] · required
**Responses**
- `200` Successful Response: SuccessResponse_ServiceItemData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceItemData · required: Service item for list/GET response. `services` is only populated on detail responses (GET by id); list endpoints return it empty to avoid per-item member queries.
- `id` · string · required
- `business_id` · string · required
- `item_name` · string · required
- `item_description` · string · required
- `item_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `cost` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `services` · ServiceData[]
- `offers` · OfferData[]
- `display_order` · integer
- `is_featured` · boolean | null
- `created_at` · integer
- `updated_at` · integer
- `deleted_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Agent API: Services
6 endpoints. HTML: https://developers.keystone.app/api/agent/services/
### GET /api/v1/agent/businesses/{business_id}/services
List services (agent)
Operation id: `list_services_api_v1_agent_businesses__business_id__services_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ServiceData__
- `request_id` · string · required
- `success` · true
- `data` · ServiceData[] · required
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/agent/businesses/{business_id}/services
Create service (agent)
Operation id: `create_service_api_v1_agent_businesses__business_id__services_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ServiceCreateBody
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `slug` · string | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_ServiceData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceData · required: Service for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/agent/businesses/{business_id}/services/{service_id}
Get service (agent)
Operation id: `get_service_api_v1_agent_businesses__business_id__services__service_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ServiceData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceData · required: Service for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/services/{service_id}
Update service (agent)
Operation id: `update_service_api_v1_agent_businesses__business_id__services__service_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ServiceUpdateBody
- `service_name` · string | null
- `service_description` · string | null
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `slug` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_ServiceData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceData · required: Service for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}/services/{service_id}
Partial update service (agent)
Operation id: `patch_service_api_v1_agent_businesses__business_id__services__service_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ServiceUpdateBody
- `service_name` · string | null
- `service_description` · string | null
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `slug` · string | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_ServiceData_
- `request_id` · string · required
- `success` · true
- `data` · ServiceData · required: Service for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `service_name` · string · required
- `service_description` · string · required
- `service_summary` · string | null
- `pricing_info` · string | null
- `features` · string | null
- `service_type` · string
- `parent_service_id` · string | null
- `slug` · string · required
- `photos` · PhotoData[]
- `service_items` · ServiceItemSummaryData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/agent/businesses/{business_id}/services/{service_id}
Delete service (agent)
Operation id: `delete_service_api_v1_agent_businesses__business_id__services__service_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `service_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Agent API: Team
6 endpoints. HTML: https://developers.keystone.app/api/agent/team/
### GET /api/v1/agent/businesses/{business_id}/team_members
List team members (agent)
Operation id: `list_team_members_api_v1_agent_businesses__business_id__team_members_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_TeamMemberData__
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData[] · required
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/agent/businesses/{business_id}/team_members
Create team member (agent)
Operation id: `create_team_member_api_v1_agent_businesses__business_id__team_members_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): TeamMemberCreateBody
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photo_ids` · string[]
**Responses**
- `201` Successful Response: SuccessResponse_TeamMemberData_
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData · required: Team member for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/agent/businesses/{business_id}/team_members/{team_member_id}
Get team member (agent)
Operation id: `get_team_member_api_v1_agent_businesses__business_id__team_members__team_member_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `team_member_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_TeamMemberData_
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData · required: Team member for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/agent/businesses/{business_id}/team_members/{team_member_id}
Update team member (agent)
Operation id: `update_team_member_api_v1_agent_businesses__business_id__team_members__team_member_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `team_member_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): TeamMemberUpdateBody
- `full_name` · string | null
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_TeamMemberData_
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData · required: Team member for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/agent/businesses/{business_id}/team_members/{team_member_id}
Partial update team member (agent)
Operation id: `patch_team_member_api_v1_agent_businesses__business_id__team_members__team_member_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `team_member_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): TeamMemberUpdateBody
- `full_name` · string | null
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photo_ids` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_TeamMemberData_
- `request_id` · string · required
- `success` · true
- `data` · TeamMemberData · required: Team member for list/GET response.
- `id` · string · required
- `business_id` · string · required
- `full_name` · string · required
- `position_title` · string | null
- `post_nominal_letters` · string | null
- `phone` · string | null
- `email` · string | null
- `bio` · string | null
- `experience` · string | null
- `specialties` · string | null
- `education` · string | null
- `certifications` · string | null
- `linkedin_url` · string | null
- `twitter_url` · string | null
- `instagram_url` · string | null
- `facebook_url` · string | null
- `youtube_url` · string | null
- `tiktok_url` · string | null
- `is_featured` · boolean | null
- `photos` · PhotoData[]
- `display_order` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/agent/businesses/{business_id}/team_members/{team_member_id}
Delete team member (agent)
Operation id: `delete_team_member_api_v1_agent_businesses__business_id__team_members__team_member_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `team_member_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
# Internal API
Service-to-service operations: onboarding, workflows, media, previews, callbacks.
Endpoints other Keystone services call: onboarding and import pipelines, the workflow engine, media processing, website preview runs, agent callbacks, and consumer auth. Not for external callers; listed so that the whole contract is in one place.
- Audience: Keystone services
- Base URL: https://sor.keystone.app
- Authentication: Internal service key, sent as the `X-Internal-Api-Key` header.
- Endpoints: 157 in 25 groups
- HTML: https://developers.keystone.app/api/internal/
## Internal API: Account invites
1 endpoints. HTML: https://developers.keystone.app/api/internal/account-invites/
### POST /api/v1/internal/account-invites/delivery-status
Record Account Invite Delivery Status
Operation id: `record_account_invite_delivery_status_api_v1_internal_account_invites_delivery_status_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): AccountInviteDeliveryStatusBody
- `invite_id` · string (uuid) | null
- `email` · string (email) | null
- `status` · string · required
- `detail` · string | null
- `event_at` · integer | null
**Responses**
- `200` Successful Response: SuccessResponse_AccountInviteDeliveryStatusData_
- `request_id` · string · required
- `success` · true
- `data` · AccountInviteDeliveryStatusData · required
- `updated` · boolean · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Account user invites
1 endpoints. HTML: https://developers.keystone.app/api/internal/account-user-invites/
### POST /api/v1/internal/account-user-invites/{invite_id}/consume
Consume Account Invite
Operation id: `consume_account_invite_api_v1_internal_account_user_invites__invite_id__consume_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `invite_id` | path | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): AccountInviteConsumeBody
- `user_id` · string (uuid) · required
- `email` · string (email) · required
- `first_name` · string · required
- `last_name` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_AccountInviteConsumeData_
- `request_id` · string · required
- `success` · true
- `data` · AccountInviteConsumeData · required
- `business_id` · string · required
- `user` · AccountUserData · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Ads workflow engine
18 endpoints. HTML: https://developers.keystone.app/api/internal/ads-workflow-engine/
### POST /api/v1/internal/workflow-engine/ads/archive-campaign
Archive Campaign
Operation id: `archive_campaign_api_v1_internal_workflow_engine_ads_archive_campaign_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolArchiveCampaignBody
- `businessId` · string (uuid) · required
- `campaignId` · string (uuid) · required
- `userConfirmed` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/create-ad-unit
Create Ad Unit
Operation id: `create_ad_unit_api_v1_internal_workflow_engine_ads_create_ad_unit_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolCreateAdUnitBody
- `businessId` · string (uuid) · required
- `adSetId` · string (uuid) · required
- `photoId` · string (uuid) · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/create-refresh-draft
Create Refresh Draft
Operation id: `create_refresh_draft_api_v1_internal_workflow_engine_ads_create_refresh_draft_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolCreateRefreshDraftBody
- `businessId` · string (uuid) · required
- `recommendationId` · string (uuid) · required
- `action` · "refresh_creative" | "refresh_audience" · required
- `photoIds` · string (uuid)[] · required
- `adCopy` · string | null
- `headline` · string | null
- `targetingAdjustments` · object | null
- `rationale` · string | null
- `mediaBrief` · object | null
- `mediaCandidates` · object[] | null
- `mediaReview` · object | null
- `workflowRunId` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/disable-all-units
Disable All Units
Operation id: `disable_all_units_api_v1_internal_workflow_engine_ads_disable_all_units_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolDisableAllUnitsBody
- `businessId` · string (uuid) · required
- `adSetId` · string (uuid) · required
- `userConfirmed` · boolean
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/draft-proposal
Draft Campaign Proposal
Operation id: `draft_campaign_proposal_api_v1_internal_workflow_engine_ads_draft_proposal_post`
Non-mutating: enrich the workflow-engine's router-extracted hints
into a complete proposal (name, ad copy, headline, address, lat/lng,
page, currency) so the ``ADS_CAMPAIGN_PROPOSAL`` widget renders
something the user can actually approve. The temporal worker calls
this just before parking at ``bp_ads_creator_proposal`` and stuffs
the response into ``extracted_data.plan``.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolDraftCampaignProposalBody
- `businessId` · string (uuid) · required
- `promotion` · string | null
- `dailyBudgetMinor` · integer | null
- `radiusMeters` · integer | null
- `objective` · string | null
- `adsAccountId` · string (uuid) | null
- `addressLine` · string | null
- `attachedPhotoIds` · string[] | null
- `adSets` · object[] | null
- `businessContext` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/generate-refresh-plan
Generate Refresh Plan
Operation id: `generate_refresh_plan_api_v1_internal_workflow_engine_ads_generate_refresh_plan_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolGenerateRefreshPlanBody
- `businessId` · string (uuid) · required
- `entityType` · "campaign" | "ad_set" | "ad_unit" · required
- `entityId` · string (uuid) · required
- `recommendationId` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/get-campaign
Get Campaign
Operation id: `get_campaign_api_v1_internal_workflow_engine_ads_get_campaign_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolGetCampaignBody
- `businessId` · string (uuid) · required
- `campaignId` · string (uuid) · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/get-insight-tile
Get Insight Tile
Operation id: `get_insight_tile_api_v1_internal_workflow_engine_ads_get_insight_tile_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolGetInsightTileBody
- `businessId` · string (uuid) · required
- `entityType` · "campaign" | "ad_set" | "ad_unit" · required
- `entityId` · string (uuid) · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/get-refresh-context
Get Refresh Context
Operation id: `get_refresh_context_api_v1_internal_workflow_engine_ads_get_refresh_context_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolGetRefreshContextBody
- `businessId` · string (uuid) · required
- `entityType` · "campaign" | "ad_set" | "ad_unit" · required
- `entityId` · string (uuid) · required
- `recommendationId` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/list-ad-sets
List Ad Sets
Operation id: `list_ad_sets_api_v1_internal_workflow_engine_ads_list_ad_sets_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolListAdSetsBody
- `businessId` · string (uuid) · required
- `campaignId` · string (uuid) · required
**Responses**
- `200` Successful Response: SuccessResponse_list_dict__
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/list-campaigns
List Campaigns
Operation id: `list_campaigns_api_v1_internal_workflow_engine_ads_list_campaigns_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolListCampaignsBody
- `businessId` · string (uuid) · required
- `status` · string | null
- `q` · string | null
- `limit` · integer
**Responses**
- `200` Successful Response: SuccessResponse_list_dict__
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/propose-campaign
Propose Campaign
Operation id: `propose_campaign_api_v1_internal_workflow_engine_ads_propose_campaign_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolProposeCampaignBody
- `businessId` · string (uuid) · required
- `plan` · object · required
- `actorUserId` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/review-refresh-media
Review Refresh Media
Operation id: `review_refresh_media_api_v1_internal_workflow_engine_ads_review_refresh_media_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolReviewRefreshMediaBody
- `businessId` · string (uuid) · required
- `mediaBrief` · object | null
- `candidates` · object[] · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/toggle-unit
Toggle Unit
Operation id: `toggle_unit_api_v1_internal_workflow_engine_ads_toggle_unit_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolToggleUnitBody
- `businessId` · string (uuid) · required
- `unitId` · string (uuid) · required
- `enabled` · boolean · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/update-ad-set
Update Ad Set
Operation id: `update_ad_set_api_v1_internal_workflow_engine_ads_update_ad_set_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolUpdateAdSetBody
- `businessId` · string (uuid) · required
- `adSetId` · string (uuid) · required
- `patch` · object · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/update-ad-unit
Update Ad Unit
Operation id: `update_ad_unit_api_v1_internal_workflow_engine_ads_update_ad_unit_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolUpdateAdUnitBody
- `businessId` · string (uuid) · required
- `unitId` · string (uuid) · required
- `patch` · object · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/update-campaign
Update Campaign
Operation id: `update_campaign_api_v1_internal_workflow_engine_ads_update_campaign_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolUpdateCampaignBody
- `businessId` · string (uuid) · required
- `campaignId` · string (uuid) · required
- `patch` · object · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/ads/update-refresh-status
Update Refresh Status
Operation id: `update_refresh_status_api_v1_internal_workflow_engine_ads_update_refresh_status_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolUpdateRefreshStatusBody
- `businessId` · string (uuid) · required
- `recommendationId` · string (uuid) · required
- `status` · "failed" | "generating" | "superseded" · required
- `workflowRunId` · string | null
- `failureCode` · string | null
- `errorMessage` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Blog posts
6 endpoints. HTML: https://developers.keystone.app/api/internal/blog-internal/
### GET /api/v1/internal/businesses/{business_id}/blog_posts
List blog posts for a business (internal)
Operation id: `list_blog_posts_api_v1_internal_businesses__business_id__blog_posts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string | no | Status bucket filter: all\|draft\|published\|pending\|archived |
| `search` | query | string \| null | no | Search title and excerpt |
| `cursor` | query | string \| null | no | Pagination cursor |
| `limit` | query | integer | no | Page size |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_BlogPostListItemData__
- `request_id` · string · required
- `success` · true
- `data` · BlogPostListItemData[] · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `thumbnail_photo_id` · string | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `excerpt` · string | null
- `is_featured` · boolean
- `media_status` · string | null
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/blog_posts
Create a blog post (internal)
Operation id: `create_blog_post_api_v1_internal_businesses__business_id__blog_posts_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): BlogPostWriteBody
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[]
- `photo_ids` · string[]
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[]
- `author_team_member_ids` · string[]
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
**Responses**
- `201` Successful Response: SuccessResponse_BlogPostData_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostData · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[] | null
- `photo_ids` · string[] | null
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[] | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `authors` · BlogPostAuthorData[]
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/blog_posts/{blog_post_id}
Get a single blog post (internal)
Operation id: `get_blog_post_api_v1_internal_businesses__business_id__blog_posts__blog_post_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `blog_post_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_BlogPostData_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostData · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[] | null
- `photo_ids` · string[] | null
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[] | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `authors` · BlogPostAuthorData[]
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/internal/businesses/{business_id}/blog_posts/{blog_post_id}
Update a blog post (internal)
Operation id: `update_blog_post_api_v1_internal_businesses__business_id__blog_posts__blog_post_id__put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `blog_post_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): BlogPostWriteBody
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[]
- `photo_ids` · string[]
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[]
- `author_team_member_ids` · string[]
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_BlogPostData_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostData · required
- `id` · string · required
- `business_id` · string · required
- `title` · string · required
- `slug` · string · required
- `status` · string · required
- `status_bucket` · string · required
- `publish_date` · string | null
- `excerpt` · string | null
- `content_markdown` · string | null
- `content_html` · string | null
- `tags` · string[] | null
- `photo_ids` · string[] | null
- `is_featured` · boolean
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[] | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
- `brief` · object | null
- `author_preview` · BlogPostAuthorPreviewData[]
- `authors` · BlogPostAuthorData[]
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/blog_posts/generate
Generate a blog post using AI (internal)
Operation id: `generate_blog_post_endpoint_api_v1_internal_businesses__business_id__blog_posts_generate_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string \| null | no | |
| `X-Workflow-Bypass` | header | string \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): BlogPostGenerateRequest
- `prompt` · string · required: User prompt describing the blog post to generate
- `provider` · string | null: Optional AI provider override: openai, anthropic, gemini
- `new_blog_candidate_id` · string (uuid) | null: A new-blog candidate id (blog_suggestions, scope new_post); its brief drives the post
**Responses**
- `200` Successful Response: SuccessResponse_BlogPostGenerateResponse_
- `request_id` · string · required
- `success` · true
- `data` · BlogPostGenerateResponse · required
- `title` · string · required
- `slug` · string · required
- `excerpt` · string · required
- `content_markdown` · string · required
- `tags` · string[]
- `media_brief` · object: Hero-photo brief for the workflow worker to resolve against the media library. Empty unless blog_media_brief_enabled is on.
- `body_images` · object[]: Inline photo briefs, each paired with a [[ks-image:N]] marker in content_markdown. Empty unless blog_body_images_enabled is on.
- `seo_title` · string | null
- `seo_description` · string | null
- `seo_keywords` · string[]
- `brief` · object | null
- `metadata` · BlogPostGenerateMetadata · required
- `generation_status` · string
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `blog_post` · BlogPostData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/blog/learnings
Writer learnings ledger for a business (internal, debugging only)
Operation id: `list_blog_learnings_api_v1_internal_businesses__business_id__blog_learnings_get`
Blog writer feedback: what the writer is currently told for this business, with evidence.
Internal only — the ledger is system-managed and never shown to users.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_dict__
- `request_id` · string · required
- `success` · true
- `data` · object[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Blog workflow engine
3 endpoints. HTML: https://developers.keystone.app/api/internal/blog-workflow-engine/
### POST /api/v1/internal/workflow-engine/blog/attach-photos
Attach Blog Photos
Operation id: `attach_blog_photos_api_v1_internal_workflow_engine_blog_attach_photos_post`
Attach photos to an existing draft without rewriting it.
The hero is resolved after the draft is stored, and a PUT would demand
title/slug/status and blank whatever it was not told.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolAttachBlogPhotosBody
- `businessId` · string (uuid) · required
- `blogPostId` · string (uuid) · required
- `photoIds` · string (uuid)[] | null
- `mediaStatus` · string | null
- `workflowRunId` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/blog/review-media
Review Blog Media
Operation id: `review_blog_media_api_v1_internal_workflow_engine_blog_review_media_post`
Vision-review hero photo candidates the worker found in the library.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolReviewBlogMediaBody
- `businessId` · string (uuid) · required
- `mediaBrief` · object | null
- `candidates` · object[] · required
- `workflowRunId` · string | null
- `ranked` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/blog/verify-hero
Verify Blog Hero
Operation id: `verify_blog_hero_api_v1_internal_workflow_engine_blog_verify_hero_post`
Check a reframed hero before it is attached to the post.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolVerifyBlogHeroBody
- `businessId` · string (uuid) · required
- `mediaBrief` · object | null
- `assetId` · string (uuid) · required
- `intent` · object
- `workflowRunId` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Consumer auth
5 endpoints. HTML: https://developers.keystone.app/api/internal/internal-consumer-auth/
### POST /api/v1/internal/consumer-auth/complete
Complete
Operation id: `complete_api_v1_internal_consumer_auth_complete_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): CompleteBody
- `business_id` · string (uuid) · required
- `phone` · string · required
- `email` · string · required
- `first_name` · string · required
- `last_name` · string · required
- `webchat_session_id` · string | null
- `client_user_agent` · string | null
- `client_ip` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_CompleteData_
- `request_id` · string · required
- `success` · true
- `data` · CompleteData · required
- `consumer_id` · string (uuid) · required
- `consumer` · ConsumerAuthProfile · required
- `event_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/consumer-auth/phone-entry
Phone Entry
Operation id: `phone_entry_api_v1_internal_consumer_auth_phone_entry_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PhoneEntryBody
- `business_id` · string (uuid) · required
- `phone` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_PhoneEntryData_
- `request_id` · string · required
- `success` · true
- `data` · PhoneEntryData · required
- `consumer_id` · string (uuid) · required
- `business_contact_id` · string (uuid) · required
- `contact_was_new` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/consumer-auth/prefill
Prefill
Operation id: `prefill_api_v1_internal_consumer_auth_prefill_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | query | string (uuid) | yes | |
| `phone` | query | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PrefillData_
- `request_id` · string · required
- `success` · true
- `data` · PrefillData · required
- `first_name` · string | null
- `last_name` · string | null
- `email` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/consumer-auth/resolve-api-key
Resolve Api Key
Operation id: `resolve_api_key_api_v1_internal_consumer_auth_resolve_api_key_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ResolveApiKeyBody
- `api_key` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_ResolveApiKeyData_
- `request_id` · string · required
- `success` · true
- `data` · ResolveApiKeyData · required
- `business_id` · string (uuid) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/consumers/{consumer_id}/portal-profile
Consumer Portal Profile
Operation id: `consumer_portal_profile_api_v1_internal_consumers__consumer_id__portal_profile_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `consumer_id` | path | string (uuid) | yes | |
| `business_id` | query | string (uuid) \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Contacts
45 endpoints. HTML: https://developers.keystone.app/api/internal/contacts/
### GET /api/v1/internal/businesses/{business_id}/contacts
List contacts
Operation id: `list_contacts_api_v1_internal_businesses__business_id__contacts_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | Filter by signal: hot,warm,cool,new,unassigned |
| `touch` | query | string \| null | no | Filter by touch: responsive,slow,unresponsive,ghosted,unassigned |
| `stage` | query | string \| null | no | Filter by stage: new,engaged,booked,returning,lapsed,lost,unassigned |
| `needs_attention` | query | boolean \| null | no | Filter contacts requiring supervisory attention |
| `identity_resolution_status` | query | string \| null | no | |
| `include_deleted` | query | boolean | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `sort` | query | string | no | Sort order. 'last_activity' (default) surfaces the most recent conversation first; also accepts any contact column (created_at, updated_at, ...). |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactData__
- `request_id` · string · required
- `success` · true
- `data` · ContactData[] · required
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts
Create contact
Operation id: `create_contact_api_v1_internal_businesses__business_id__contacts_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactCreateBody
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `email` · string | null
- `phone` · string | null
- `emails` · ContactEmailInputBody[]
- `email` · string · required
- `source` · string | null
- `is_primary` · boolean
- `phones` · ContactPhoneInputBody[]
- `phone` · string · required
- `source` · string | null
- `is_primary` · boolean
- `status` · string
- `lifecycle_stage` · string
- `metadata_` · object | null
- `source` · ContactSourceBody | null
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `consent` · ContactConsentBody | null
- `transactional_sms` · boolean | null
- `marketing_sms` · boolean | null
- `marketing_email` · boolean | null
- `tos_privacy` · boolean | null
- `ai_initiate` · boolean
**Responses**
- `201` Successful Response: SuccessResponse_ContactOutcomeData_
- `request_id` · string · required
- `success` · true
- `data` · ContactOutcomeData · required: Structured response for POST /contacts and PUT /contacts/upsert.
- `contact` · ContactData · required: Contact detail / list item response shape.
- `operation` · string · required
- `consumer_link_status` · string · required
- `duplicate_flagged` · boolean
- `identity_review_id` · string | null
- `duplicate_review_id` · string | null
- `ai_initiation` · AiInitiationResult | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}
Get contact detail
Operation id: `get_contact_api_v1_internal_businesses__business_id__contacts__contact_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/internal/businesses/{business_id}/contacts/{contact_id}
Update contact
Operation id: `update_contact_api_v1_internal_businesses__business_id__contacts__contact_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactUpdateBody
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string | null
- `lifecycle_stage` · string | null
- `signal` · string | null
- `touch` · string | null
- `stage` · string | null
- `metadata_` · object | null
- `last_contacted_at` · string (date-time) | null
- `auto_contact_enabled` · boolean | null
- `outreach_suppressed` · boolean | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/internal/businesses/{business_id}/contacts/{contact_id}
Delete contact
Operation id: `delete_contact_api_v1_internal_businesses__business_id__contacts__contact_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/classification
Override contact classification
Operation id: `patch_classification_api_v1_internal_businesses__business_id__contacts__contact_id__classification_patch`
Override one or more classification dimensions for the contact. Any field omitted in the body is left unchanged. Each changed dimension lands a row in `contact_classification_history` with `changed_by="user:api"` and the supplied `reason`.
Manual overrides are NOT sticky — the next AI classification pass (triggered by inbound messages or DELIVERED/READ status events) may overwrite them. To preserve a manual value, take the conversation out of AUTONOMOUS AI mode.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ClassifyBody
- `signal` · string | null
- `touch` · string | null
- `stage` · string | null
- `reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/classification-history
Get classification history
Operation id: `get_classification_history_api_v1_internal_businesses__business_id__contacts__contact_id__classification_history_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `field` | query | string \| null | no | Filter by: signal, touch, stage |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ClassificationHistoryItem__
- `request_id` · string · required
- `success` · true
- `data` · ClassificationHistoryItem[] · required
- `type` · string
- `field` · string · required
- `old_value` · string | null
- `new_value` · string · required
- `changed_by` · string · required
- `reason` · string | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/classify
Manually classify a contact (legacy POST alias)
Operation id: `classify_contact_api_v1_internal_businesses__business_id__contacts__contact_id__classify_post`
Override one or more classification dimensions for the contact. Any field omitted in the body is left unchanged. Each changed dimension lands a row in `contact_classification_history` with `changed_by="user:api"` and the supplied `reason`.
Manual overrides are NOT sticky — the next AI classification pass (triggered by inbound messages or DELIVERED/READ status events) may overwrite them. To preserve a manual value, take the conversation out of AUTONOMOUS AI mode.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ClassifyBody
- `signal` · string | null
- `touch` · string | null
- `stage` · string | null
- `reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/conversation
Get this contact's conversation
Operation id: `get_contact_conversation_api_v1_internal_businesses__business_id__contacts__contact_id__conversation_get`
Returns the conversation for this contact's consumer, or `data: null` with 200 when none exists yet. 404 is reserved for the contact itself being missing from this business — the frontend should branch on `data == null`, not on status code.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_Union_ConversationData__NoneType__
- `request_id` · string · required
- `success` · true
- `data` · ConversationData | null · required
- `id` · string · required
- `consumer_id` · string | null
- `status` · string · required
- `assigned_to` · integer | null
- `ai_enabled` · boolean
- `ai_mode` · string
- `last_message_at` · string (date-time) | null
- `last_message_preview` · string | null
- `last_message_channel` · string | null
- `unread_count` · integer
- `message_count` · integer
- `snoozed_until` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `reply_channel` · ReplyChannelData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/emails
List contact emails
Operation id: `list_emails_api_v1_internal_businesses__business_id__contacts__contact_id__emails_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactEmailData__
- `request_id` · string · required
- `success` · true
- `data` · ContactEmailData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `business_id` · string · required
- `raw_input` · string | null
- `email` · string · required
- `verified_at` · string (date-time) | null
- `source` · string | null
- `is_deleted` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/emails
Add email to contact
Operation id: `add_email_api_v1_internal_businesses__business_id__contacts__contact_id__emails_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactEmailCreateBody
- `email` · string · required
- `source` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_ContactEmailAddResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactEmailAddResult · required: Response for POST /contacts/{id}/emails.
- `email` · ContactEmailData · required: Email record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/emails/{email_id}
Update contact email
Operation id: `update_email_api_v1_internal_businesses__business_id__contacts__contact_id__emails__email_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `email_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactEmailPatchBody
- `email` · string | null
- `source` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactEmailUpdateResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactEmailUpdateResult · required: Response for PATCH /contacts/{id}/emails/{email_id}. Mirrors `ContactEmailAddResult` so the console can reuse the same response handler — `identity_review_id` is set when a value change on a consumer-linked contact opens (or finds an existing) review.
- `email` · ContactEmailData · required: Email record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/emails/{email_id}
Remove email from contact
Operation id: `remove_email_api_v1_internal_businesses__business_id__contacts__contact_id__emails__email_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `email_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/emails/{email_id}/set-primary
Set primary email
Operation id: `set_primary_email_api_v1_internal_businesses__business_id__contacts__contact_id__emails__email_id__set_primary_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `email_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/insights
List contact insights
Operation id: `list_contact_insights_api_v1_internal_businesses__business_id__contacts__contact_id__insights_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `include_dismissed` | query | boolean | no | |
| `priority` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactInsightData__
- `request_id` · string · required
- `success` · true
- `data` · ContactInsightData[] · required
- `id` · string · required
- `contact_id` · string · required
- `business_id` · string · required
- `insight_type` · string · required
- `title` · string · required
- `body` · string · required
- `priority` · string · required
- `action_type` · string | null
- `action_data` · object | null
- `is_dismissed` · boolean
- `expires_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/insights/{insight_id}
Get a contact insight
Operation id: `get_contact_insight_api_v1_internal_businesses__business_id__contacts__contact_id__insights__insight_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `insight_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactInsightData_
- `request_id` · string · required
- `success` · true
- `data` · ContactInsightData · required: Contact insight response shape.
- `id` · string · required
- `contact_id` · string · required
- `business_id` · string · required
- `insight_type` · string · required
- `title` · string · required
- `body` · string · required
- `priority` · string · required
- `action_type` · string | null
- `action_data` · object | null
- `is_dismissed` · boolean
- `expires_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/insights/{insight_id}/dismiss
Dismiss a contact insight
Operation id: `dismiss_contact_insight_api_v1_internal_businesses__business_id__contacts__contact_id__insights__insight_id__dismiss_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `insight_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactInsightData_
- `request_id` · string · required
- `success` · true
- `data` · ContactInsightData · required: Contact insight response shape.
- `id` · string · required
- `contact_id` · string · required
- `business_id` · string · required
- `insight_type` · string · required
- `title` · string · required
- `body` · string · required
- `priority` · string · required
- `action_type` · string | null
- `action_data` · object | null
- `is_dismissed` · boolean
- `expires_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/needs-attention
Resolve contact needs-attention overlay
Operation id: `resolve_contact_needs_attention_api_v1_internal_businesses__business_id__contacts__contact_id__needs_attention_delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `re_enable_ai` | query | boolean | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: object
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/opt-in
Record opt-in
Operation id: `opt_in_api_v1_internal_businesses__business_id__contacts__contact_id__opt_in_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): OptInOutBody
- `channel` · string · required
- `source` · string · required
- `occurred_at` · string (date-time) | null
- `idempotency_key` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactPreferenceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactPreferenceData · required: GET /contacts/{id}/preferences response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `transactional_sms_opt_in_at` · string (date-time) | null
- `transactional_sms_opt_out_at` · string (date-time) | null
- `marketing_sms_opt_in_at` · string (date-time) | null
- `marketing_sms_opt_out_at` · string (date-time) | null
- `marketing_email_opt_in_at` · string (date-time) | null
- `marketing_email_opt_out_at` · string (date-time) | null
- `tos_privacy_accepted_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/opt-out
Record opt-out
Operation id: `opt_out_api_v1_internal_businesses__business_id__contacts__contact_id__opt_out_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): OptInOutBody
- `channel` · string · required
- `source` · string · required
- `occurred_at` · string (date-time) | null
- `idempotency_key` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactPreferenceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactPreferenceData · required: GET /contacts/{id}/preferences response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `transactional_sms_opt_in_at` · string (date-time) | null
- `transactional_sms_opt_out_at` · string (date-time) | null
- `marketing_sms_opt_in_at` · string (date-time) | null
- `marketing_sms_opt_out_at` · string (date-time) | null
- `marketing_email_opt_in_at` · string (date-time) | null
- `marketing_email_opt_out_at` · string (date-time) | null
- `tos_privacy_accepted_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/phones
List contact phones
Operation id: `list_phones_api_v1_internal_businesses__business_id__contacts__contact_id__phones_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactPhoneData__
- `request_id` · string · required
- `success` · true
- `data` · ContactPhoneData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `business_id` · string · required
- `raw_input` · string | null
- `phone` · string · required
- `phone_type` · string | null
- `country_code` · string | null
- `verified_at` · string (date-time) | null
- `source` · string | null
- `is_deleted` · boolean
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/phones
Add phone to contact
Operation id: `add_phone_api_v1_internal_businesses__business_id__contacts__contact_id__phones_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactPhoneCreateBody
- `phone` · string · required
- `source` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_ContactPhoneAddResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactPhoneAddResult · required: Response for POST /contacts/{id}/phones.
- `phone` · ContactPhoneData · required: Phone record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/phones/{phone_id}
Update contact phone
Operation id: `update_phone_api_v1_internal_businesses__business_id__contacts__contact_id__phones__phone_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `phone_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactPhonePatchBody
- `phone` · string | null
- `phone_type` · string | null
- `country_code` · string | null
- `source` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactPhoneUpdateResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactPhoneUpdateResult · required: Response for PATCH /contacts/{id}/phones/{phone_id}. Mirrors `ContactPhoneAddResult` so the console can reuse the same response handler — `identity_review_id` is set when a value change on a consumer-linked contact opens (or finds an existing) review.
- `phone` · ContactPhoneData · required: Phone record response shape.
- `identity_review_id` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### DELETE /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/phones/{phone_id}
Remove phone from contact
Operation id: `remove_phone_api_v1_internal_businesses__business_id__contacts__contact_id__phones__phone_id__delete`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `phone_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/phones/{phone_id}/set-primary
Set primary phone
Operation id: `set_primary_phone_api_v1_internal_businesses__business_id__contacts__contact_id__phones__phone_id__set_primary_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `phone_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/preferences
Get preferences
Operation id: `get_preferences_api_v1_internal_businesses__business_id__contacts__contact_id__preferences_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ContactPreferenceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactPreferenceData · required: GET /contacts/{id}/preferences response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `transactional_sms_opt_in_at` · string (date-time) | null
- `transactional_sms_opt_out_at` · string (date-time) | null
- `marketing_sms_opt_in_at` · string (date-time) | null
- `marketing_sms_opt_out_at` · string (date-time) | null
- `marketing_email_opt_in_at` · string (date-time) | null
- `marketing_email_opt_out_at` · string (date-time) | null
- `tos_privacy_accepted_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/reassess
Trigger a fresh AI classification pass
Operation id: `reassess_contact_api_v1_internal_businesses__business_id__contacts__contact_id__reassess_post`
Publishes a manual `ClassificationRequest` for this contact's consumer. The classification consumer picks it up out-of-band and writes back signal/touch/stage + a fresh `latest_insight`; expect the new values to land within seconds. Returns the current contact snapshot — poll the same contact-detail endpoint to see the updated classification.
Returns 202 even when Kafka is disabled — the next inbound message will pick the contact up; the response includes the current state regardless.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `202` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/score-history
Get engagement score history
Operation id: `get_score_history_api_v1_internal_businesses__business_id__contacts__contact_id__score_history_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ScoreHistoryItem__
- `request_id` · string · required
- `success` · true
- `data` · ScoreHistoryItem[] · required
- `engagement_score` · integer · required
- `purchase_intent` · integer | null
- `model_version` · string | null
- `reasoning` · string | null
- `input_signals` · object | null
- `created_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/sources
List sources
Operation id: `list_sources_api_v1_internal_businesses__business_id__contacts__contact_id__sources_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactSourceData__
- `request_id` · string · required
- `success` · true
- `data` · ContactSourceData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/{contact_id}/sources
Add source
Operation id: `add_source_api_v1_internal_businesses__business_id__contacts__contact_id__sources_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `contact_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactSourceCreateBody
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_ContactSourceData_
- `request_id` · string · required
- `success` · true
- `data` · ContactSourceData · required: Source attribution response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/export
Export contacts as CSV
Operation id: `export_contacts_api_v1_internal_businesses__business_id__contacts_export_get`
Stream every matching contact as a CSV file (one server request, no client-side paging).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `q` | query | string \| null | no | Optional search query; when set, matches the search view |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | |
| `touch` | query | string \| null | no | |
| `stage` | query | string \| null | no | |
| `needs_attention` | query | boolean \| null | no | |
| `identity_resolution_status` | query | string \| null | no | |
| `include_deleted` | query | boolean | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/lookup
Lookup contact by email/phone
Operation id: `lookup_contacts_api_v1_internal_businesses__business_id__contacts_lookup_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactLookupBody
- `email` · string | null
- `phone` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactLookupResult_
- `request_id` · string · required
- `success` · true
- `data` · ContactLookupResult · required: POST /contacts/lookup response data.
- `match_type` · string · required
- `contacts` · ContactData[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/contacts/merge
Merge contacts
Operation id: `merge_contacts_api_v1_internal_businesses__business_id__contacts_merge_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactMergeBody
- `surviving_contact_id` · string (uuid) · required
- `merged_contact_id` · string (uuid) · required
- `field_resolutions` · object | null
- `merge_reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_ContactData_
- `request_id` · string · required
- `success` · true
- `data` · ContactData · required: Contact detail / list item response shape.
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/needs-attention
List contacts needing attention
Operation id: `get_needs_attention_api_v1_internal_businesses__business_id__contacts_needs_attention_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_NeedsAttentionData_
- `request_id` · string · required
- `success` · true
- `data` · NeedsAttentionData · required: GET /contacts/needs-attention response shape.
- `items` · NeedsAttentionItem[] · required
- `total` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/pipeline-summary
Get pipeline summary
Operation id: `get_pipeline_summary_api_v1_internal_businesses__business_id__contacts_pipeline_summary_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_PipelineSummaryData_
- `request_id` · string · required
- `success` · true
- `data` · PipelineSummaryData · required: GET /contacts/pipeline-summary response shape.
- `summary` · string · required
- `stats` · object · required
- `generated_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/search
Search contacts
Operation id: `search_contacts_api_v1_internal_businesses__business_id__contacts_search_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `q` | query | string | yes | |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | Filter by signal: hot,warm,cool,new,unassigned |
| `touch` | query | string \| null | no | Filter by touch: responsive,slow,unresponsive,ghosted,unassigned |
| `stage` | query | string \| null | no | Filter by stage: new,engaged,booked,returning,lapsed,lost,unassigned |
| `needs_attention` | query | boolean \| null | no | Filter contacts requiring supervisory attention |
| `identity_resolution_status` | query | string \| null | no | |
| `include_deleted` | query | boolean | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `sort` | query | string | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_ContactData__
- `request_id` · string · required
- `success` · true
- `data` · ContactData[] · required
- `id` · string · required
- `business_id` · string · required
- `consumer_id` · string | null
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `status` · string · required
- `lifecycle_stage` · string · required
- `identity_resolution_status` · string · required
- `signal` · string
- `touch` · string
- `stage` · string
- `engagement_score` · integer | null
- `purchase_intent` · integer | null
- `primary_email` · string | null
- `primary_phone` · string | null
- `last_contacted_at` · string (date-time) | null
- `last_replied_at` · string (date-time) | null
- `last_inbound_at` · string (date-time) | null
- `last_outbound_at` · string (date-time) | null
- `needs_attention` · boolean
- `needs_attention_reason` · string | null
- `needs_attention_at` · string (date-time) | null
- `metadata_` · object
- `is_deleted` · boolean
- `deleted_at` · string (date-time) | null
- `merged_into_contact_id` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `first_source_type` · string | null
- `first_source_detail` · string | null
- `first_source_message` · string | null
- `latest_insight` · LatestInsightSummary | null
- `conversation_id` · string | null
- `message_count` · integer
- `auto_contact_enabled` · boolean
- `outreach_suppressed` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/contacts/stats
Get classification stats
Operation id: `get_classification_stats_api_v1_internal_businesses__business_id__contacts_stats_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `lifecycle_stage` | query | string \| null | no | |
| `signal` | query | string \| null | no | |
| `touch` | query | string \| null | no | |
| `stage` | query | string \| null | no | |
| `needs_attention` | query | boolean \| null | no | |
| `identity_resolution_status` | query | string \| null | no | |
| `source` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `updated_after` | query | string (date-time) \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ClassificationStatsData_
- `request_id` · string · required
- `success` · true
- `data` · ClassificationStatsData · required: GET /contacts/stats response shape.
- `total` · integer · required
- `signal` · object · required
- `touch` · object · required
- `stage` · object · required
- `lifecycle_stage` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PUT /api/v1/internal/businesses/{business_id}/contacts/upsert
Upsert contact
Operation id: `upsert_contact_api_v1_internal_businesses__business_id__contacts_upsert_put`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ContactCreateBody
- `first_name` · string | null
- `last_name` · string | null
- `display_name` · string | null
- `email` · string | null
- `phone` · string | null
- `emails` · ContactEmailInputBody[]
- `email` · string · required
- `source` · string | null
- `is_primary` · boolean
- `phones` · ContactPhoneInputBody[]
- `phone` · string · required
- `source` · string | null
- `is_primary` · boolean
- `status` · string
- `lifecycle_stage` · string
- `metadata_` · object | null
- `source` · ContactSourceBody | null
- `contact_source_type` · ContactSourceType · required: Allowed contact source values.
- `contact_source_detail` · string | null
- `external_ref_id` · string | null
- `consent` · ContactConsentBody | null
- `transactional_sms` · boolean | null
- `marketing_sms` · boolean | null
- `marketing_email` · boolean | null
- `tos_privacy` · boolean | null
- `ai_initiate` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_ContactOutcomeData_
- `request_id` · string · required
- `success` · true
- `data` · ContactOutcomeData · required: Structured response for POST /contacts and PUT /contacts/upsert.
- `contact` · ContactData · required: Contact detail / list item response shape.
- `operation` · string · required
- `consumer_link_status` · string · required
- `duplicate_flagged` · boolean
- `identity_review_id` · string | null
- `duplicate_review_id` · string | null
- `ai_initiation` · AiInitiationResult | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/duplicate-reviews
List duplicate reviews
Operation id: `list_duplicate_reviews_api_v1_internal_businesses__business_id__duplicate_reviews_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_DuplicateReviewData__
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData[] · required
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/duplicate-reviews/{review_id}
Get duplicate review
Operation id: `get_duplicate_review_api_v1_internal_businesses__business_id__duplicate_reviews__review_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_DuplicateReviewData_
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData · required: Duplicate review response shape.
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/duplicate-reviews/{review_id}/dismiss
Dismiss duplicate pair
Operation id: `dismiss_duplicate_pair_api_v1_internal_businesses__business_id__duplicate_reviews__review_id__dismiss_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): DuplicateReviewDismissBody
- `reason` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_DuplicateReviewData_
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData · required: Duplicate review response shape.
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/duplicate-reviews/{review_id}/merge
Merge duplicate pair
Operation id: `merge_duplicate_pair_api_v1_internal_businesses__business_id__duplicate_reviews__review_id__merge_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): DuplicateReviewMergeBody
- `surviving_contact_id` · string · required
- `field_resolutions` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_DuplicateReviewData_
- `request_id` · string · required
- `success` · true
- `data` · DuplicateReviewData · required: Duplicate review response shape.
- `id` · string · required
- `business_id` · string · required
- `contact_id_a` · string · required
- `contact_id_b` · string · required
- `match_type` · string · required
- `confidence_score` · number | null
- `status` · string · required
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact_a` · ContactSummary | null
- `contact_b` · ContactSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/identity-reviews
List identity reviews
Operation id: `list_identity_reviews_api_v1_internal_businesses__business_id__identity_reviews_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `status` | query | string \| null | no | |
| `business_contact_id` | query | string (uuid) \| null | no | |
| `email_matched_consumer_id` | query | string (uuid) \| null | no | |
| `phone_matched_consumer_id` | query | string (uuid) \| null | no | |
| `created_after` | query | string (date-time) \| null | no | |
| `created_before` | query | string (date-time) \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_list_IdentityReviewData__
- `request_id` · string · required
- `success` · true
- `data` · IdentityReviewData[] · required
- `id` · string · required
- `business_contact_id` · string · required
- `email_matched_consumer_id` · string | null
- `phone_matched_consumer_id` · string | null
- `conflict_snapshot` · object · required
- `status` · string · required
- `resolved_consumer_id` · string | null
- `resolution_action` · string | null
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `notes` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact` · ContactSummary | null
- `email_matched_consumer` · ConsumerSummary | null
- `phone_matched_consumer` · ConsumerSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/identity-reviews/{review_id}
Get identity review
Operation id: `get_identity_review_api_v1_internal_businesses__business_id__identity_reviews__review_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_IdentityReviewData_
- `request_id` · string · required
- `success` · true
- `data` · IdentityReviewData · required: Identity review response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `email_matched_consumer_id` · string | null
- `phone_matched_consumer_id` · string | null
- `conflict_snapshot` · object · required
- `status` · string · required
- `resolved_consumer_id` · string | null
- `resolution_action` · string | null
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `notes` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact` · ContactSummary | null
- `email_matched_consumer` · ConsumerSummary | null
- `phone_matched_consumer` · ConsumerSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/identity-reviews/{review_id}/resolve
Resolve identity review
Operation id: `resolve_identity_review_api_v1_internal_businesses__business_id__identity_reviews__review_id__resolve_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `review_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): IdentityReviewResolveBody
- `action` · "link_to_consumer" | "merge_consumers_and_link" | "create_new_consumer" | "ignore" · required
- `target_consumer_id` · string | null
- `notes` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_IdentityReviewData_
- `request_id` · string · required
- `success` · true
- `data` · IdentityReviewData · required: Identity review response shape.
- `id` · string · required
- `business_contact_id` · string · required
- `email_matched_consumer_id` · string | null
- `phone_matched_consumer_id` · string | null
- `conflict_snapshot` · object · required
- `status` · string · required
- `resolved_consumer_id` · string | null
- `resolution_action` · string | null
- `resolved_by` · string | null
- `resolved_at` · string (date-time) | null
- `notes` · string | null
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `contact` · ContactSummary | null
- `email_matched_consumer` · ConsumerSummary | null
- `phone_matched_consumer` · ConsumerSummary | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Data models
2 endpoints. HTML: https://developers.keystone.app/api/internal/internal-data-models/
### GET /api/v1/internal/businesses/{business_id}/data-models
List mutable business data models (workflow catalog)
Operation id: `list_data_models_api_v1_internal_businesses__business_id__data_models_get`
Return catalog of entity types the agent workflow may update. Validates business exists.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_DataModelCatalogData_
- `request_id` · string · required
- `success` · true
- `data` · DataModelCatalogData · required: GET /internal/businesses/{id}/data-models response data.
- `business_id` · string · required
- `models` · DataModelCatalogEntry[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/data-models/{entity}/update-schema
JSON Schema for create/update bodies of a business data entity
Operation id: `get_update_schema_api_v1_internal_businesses__business_id__data_models__entity__update_schema_get`
Return Pydantic-derived JSON Schemas for the entity's create and update bodies.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `entity` | path | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_DataModelUpdateSchemaData_
- `request_id` · string · required
- `success` · true
- `data` · DataModelUpdateSchemaData · required: GET .../data-models/{entity}/update-schema response data.
- `entity_id` · string · required
- `display_name` · string · required
- `requires_target_identifier_for_modify_delete` · boolean
- `target_identifier_hint` · string | null
- `create_body_json_schema` · object · required
- `update_body_json_schema` · object · required
- `fields_summary` · FieldSummaryRow[] · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Follow ups
1 endpoints. HTML: https://developers.keystone.app/api/internal/follow-ups/
### POST /api/v1/internal/follow-ups/execute
Execute Follow Up
Operation id: `execute_follow_up_api_v1_internal_follow_ups_execute_post`
Kairos callback for a previously scheduled follow-up. Inserts an
OUTBOUND message and publishes it through the standard outbound flow.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): FollowUpExecuteBody
- `conversation_id` · string (uuid) · required
- `consumer_id` · string (uuid) · required
- `triggering_message_id` · string (uuid) · required
**Responses**
- `200` Successful Response: SuccessResponse_FollowUpExecuteData_
- `request_id` · string · required
- `success` · true
- `data` · FollowUpExecuteData · required
- `message_id` · string (uuid) | null
- `skipped` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Import data
1 endpoints. HTML: https://developers.keystone.app/api/internal/import-data-internal/
### POST /api/v1/internal/import-data/scrape-media-backfill
Promote one bounded batch of scraped image assets into media libraries
Operation id: `scrape_media_backfill_api_v1_internal_import_data_scrape_media_backfill_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `max_assets` | query | integer | no | |
| `business_id` | query | string (uuid) \| null | no | Restrict the sweep to one business |
| `dry_run` | query | boolean | no | Report counts only; no copies or writes |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ScrapeMediaBackfillResult_
- `request_id` · string · required
- `success` · true
- `data` · ScrapeMediaBackfillResult · required: One bounded sweep of the scrape-asset → media-library backfill (KS-1613). Invariant: ``processed == promoted + skipped_duplicate + skipped_copy_failed``. ``remaining`` counts still-eligible assets after this call — the caller's loop driver. ``unpromotable_unconfigured`` is informational (assets whose blob was never stored; they are never processed).
- `processed` · integer · required
- `promoted` · integer · required
- `skipped_duplicate` · integer · required
- `skipped_copy_failed` · integer · required
- `remaining` · integer · required
- `unpromotable_unconfigured` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Media
8 endpoints. HTML: https://developers.keystone.app/api/internal/media-internal/
### GET /api/v1/internal/media/businesses
Businesses with >=1 live photo — media-service bulk-backfill enumeration (internal)
Operation id: `list_media_businesses_api_v1_internal_media_businesses_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: InternalMediaBusinessListData
- `businesses` · InternalMediaBusinessItem[] · required
- `business_id` · string · required
- `photo_count` · integer · required
- `next_cursor` · string | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/media/businesses/{business_id}/members/{user_id}
Is this heimdal user a member of this business? — media-service tenant check (internal)
Operation id: `get_media_business_membership_api_v1_internal_media_businesses__business_id__members__user_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `user_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: BusinessMembershipData
- `business_id` · string (uuid) · required
- `user_id` · string (uuid) · required
- `is_member` · boolean · required
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/media/gallery
Paged industry-gallery list for the media-service industry sweep (internal)
Operation id: `list_media_gallery_api_v1_internal_media_gallery_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `industry_id` | query | string \| null | no | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: InternalGalleryPhotoListData
- `items` · InternalGalleryPhotoItem[] · required
- `asset_id` · string · required
- `industry_id` · string | null
- `photo_source` · string
- `public_url` · string | null
- `storage` · MediaStorageRef | null
- `mime_type` · string | null
- `width_px` · integer | null
- `height_px` · integer | null
- `created_at` · string | null
- `next_cursor` · string | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/media/gallery/{photo_id}/bytes
Stream one industry-gallery photo's blob (internal)
Operation id: `get_media_gallery_photo_bytes_api_v1_internal_media_gallery__photo_id__bytes_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `photo_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/media/photos
Paged photo list for media-service ingest/backfill/sweep (internal)
Operation id: `list_media_photos_api_v1_internal_media_photos_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | query | string (uuid) | yes | |
| `cursor` | query | string \| null | no | |
| `limit` | query | integer | no | |
| `include_deleted` | query | boolean | no | |
| `updated_after` | query | string \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: InternalMediaPhotoListData
- `items` · InternalMediaPhotoItem[] · required
- `asset_id` · string · required
- `business_id` · string · required
- `photo_source` · string · required
- `public_url` · string | null
- `storage` · MediaStorageRef | null
- `external` · MediaExternalRef | null
- `mime_type` · string | null
- `width_px` · integer | null
- `height_px` · integer | null
- `is_deleted` · boolean
- `created_at` · string | null
- `updated_at` · string | null
- `next_cursor` · string | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/media/photos/{photo_id}/bytes
Stream one photo's blob (internal); 410 when an external URL expired
Operation id: `get_media_photo_bytes_api_v1_internal_media_photos__photo_id__bytes_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `photo_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/media/photos/{photo_id}/usages
Delete-guard cross-check: modules referencing this photo (internal)
Operation id: `get_media_photo_usages_api_v1_internal_media_photos__photo_id__usages_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `photo_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: PhotoUsagesData
- `asset_id` · string · required
- `in_use` · boolean · required
- `usages` · PhotoUsageItem[] · required
- `module` · string · required
- `label` · string · required
- `count` · integer · required
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/media/photos/import-derivative
Write an edited image back as a first-class derived photo (internal)
Operation id: `import_media_derivative_api_v1_internal_media_photos_import_derivative_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (multipart/form-data, required): Body_import_media_derivative_api_v1_internal_media_photos_import_derivative_post
- `file` · string · required
- `meta` · string · required
**Responses**
- `201` Successful Response: ImportDerivativeData
- `asset_id` · string · required
- `public_url` · string | null
- `photo_source` · string
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Notifications
1 endpoints. HTML: https://developers.keystone.app/api/internal/notifications/
### POST /api/v1/internal/{provider}/notifications
Handle Listing Provider Notifications
Operation id: `handle_listing_provider_notifications_api_v1_internal__provider__notifications_post`
Handle listing provider push envelope (e.g. Pub/Sub OIDC ``Authorization: Bearer``).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `provider` | path | ListingProvider | yes | |
| `authorization` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_ListingNotificationHandleResponseData_
- `request_id` · string · required
- `success` · true
- `data` · ListingNotificationHandleResponseData · required: Response data for ``POST /{provider}/notifications`` (push ingress).
- `result` · string · required
- `enqueued` · boolean · required
- `reason` · string | null
- `event` · object · required
- `resolved` · object | object[] | null
- `failures` · object[] | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Onboarding
21 endpoints. HTML: https://developers.keystone.app/api/internal/onboarding-internal/
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/assets
Ingest Assets
Operation id: `ingest_assets_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__assets_post`
Checkpointed batch of downloaded+stored assets (metadata + GCS blob name
+ DOM signals). Idempotent by (crawl_attempt_id, asset_url).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): IngestAssetsBody
- `assets` · IngestAssetItem[]
- `asset_url` · string · required
- `asset_type` · string · required
- `page_id` · string | null
- `asset_host` · string | null
- `mime_type` · string | null
- `alt_text` · string | null
- `source_page_url` · string | null
- `width` · integer | null
- `height` · integer | null
- `byte_size` · integer | null
- `checksum_sha256` · string | null
- `download_status` · string | null
- `download_error` · string | null
- `storage_status` · string | null
- `storage_error` · string | null
- `storage_provider` · string | null
- `storage_bucket` · string | null
- `storage_blob_name` · string | null
- `dom_signals` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_IngestAssetsData_
- `request_id` · string · required
- `success` · true
- `data` · IngestAssetsData · required
- `created` · integer
- `updated` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/classify
Classify Crawl Pages
Operation id: `classify_crawl_pages_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__classify_post`
Label every crawled page and return the fan-out routing table (W3).
One cheap batched LLM call over compact page descriptors. The response is the
engine's whole work list: ``batches`` (one extract-domain call each),
``blog_post_page_ids`` (one extract-blog-post call each) and
``other_page_ids``. Every page appears in at least one of the three — a page
the classifier missed is assigned ``other`` and reported in
``unrouted_page_ids``, never dropped.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_ClassifyPagesData_
- `request_id` · string · required
- `success` · true
- `data` · ClassifyPagesData · required
- `routing` · object
- `batches` · ClassifyBatchData[]
- `blog_post_page_ids` · string[]
- `other_page_ids` · string[]
- `unrouted_page_ids` · string[]
- `page_count` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/complete
Complete Crawl
Operation id: `complete_crawl_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__complete_post`
Finalize the crawl attempt. On ``completed`` runs media promotion inline
(scoped to this attempt) so scraped images enter the photo library.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): CompleteCrawlBody
- `status` · string · required
- `counters` · CompleteCrawlCounters | null
- `pages_scraped` · integer | null
- `pages_succeeded` · integer | null
- `pages_failed` · integer | null
- `assets_discovered` · integer | null
- `summary_json` · object | null
- `error_message` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_CompleteCrawlData_
- `request_id` · string · required
- `success` · true
- `data` · CompleteCrawlData · required
- `status` · string · required
- `promoted` · integer
- `media_promoted` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/extract-blog-post
Extract Blog Post Fragment
Operation id: `extract_blog_post_fragment_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__extract_blog_post_post`
Extract one blog post from one page (W5).
The finest fan-out grain: one call per post is what restores full article
bodies. The engine treats a post that exhausts its retries as best-effort —
it drops the fragment and merge's CSS fallback covers the post.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ExtractBlogPostBody
- `page_id` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_ExtractBlogPostData_
- `request_id` · string · required
- `success` · true
- `data` · ExtractBlogPostData · required
- `page_id` · string · required
- `fragment` · object
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/extract-domain
Extract Domain Fragment
Operation id: `extract_domain_fragment_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__extract_domain_post`
Extract one routed batch (≤5 pages) for one data domain (W4).
The batch's full page content plus the domain's current-SOR slice go into one
LLM call whose whole output budget belongs to this batch. Returns a
payload-shaped fragment — off-domain collections included, because an FAQ
sitting on a service page must survive a classifier mislabel.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ExtractDomainBody
- `domain` · string · required
- `page_ids` · string[]
**Responses**
- `200` Successful Response: SuccessResponse_ExtractDomainData_
- `request_id` · string · required
- `success` · true
- `data` · ExtractDomainData · required
- `domain` · string · required
- `page_ids` · string[]
- `fragment` · object
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/extract-findings
Extract Findings
Operation id: `extract_findings_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__extract_findings_post`
Sweep every crawled page for off-schema value → typed ``extra:*`` contexts (W6).
Additive by design: an empty crawl returns an empty list rather than failing,
and malformed entries are dropped instead of costing the good ones.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ExtractFindingsBody
- `captured_domains` · string[]
**Responses**
- `200` Successful Response: SuccessResponse_ExtractFindingsData_
- `request_id` · string · required
- `success` · true
- `data` · ExtractFindingsData · required
- `contexts` · TypedContextData[]
- `pages_reviewed` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/fetched-urls
Fetched Urls
Operation id: `fetched_urls_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__fetched_urls_get`
Normalized URLs already persisted for this attempt — the scrape job seeds
its visited-set from this so a retry skips completed pages.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_FetchedUrlsData_
- `request_id` · string · required
- `success` · true
- `data` · FetchedUrlsData · required
- `urls` · string[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/heartbeat
Heartbeat Crawl Attempt
Operation id: `heartbeat_crawl_attempt_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__heartbeat_post`
Proof of life from the scrape job, independent of ingest traffic.
Pages reach sor per batch and assets only after the crawl, so a job can be
busy for longer than the stale window without calling ``/pages`` or
``/assets`` — most plainly a retry that re-crawls the pages a killed
execution already persisted. The job beats this on a timer for the whole
crawl + asset pass. No-op (``beat=false``) once the attempt is terminal, so
a superseded execution cannot keep a finished run looking alive.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CrawlHeartbeatData_
- `request_id` · string · required
- `success` · true
- `data` · CrawlHeartbeatData · required: ``beat`` is False when the attempt is already terminal: a late beat from a superseded execution must not keep the run looking alive.
- `beat` · boolean · required
- `last_heartbeat_at` · integer | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/pages
List Pages
Operation id: `list_pages_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__pages_get`
Pages of a crawl attempt, in one of two shapes (design §11).
``fields=descriptor`` (default): compact classifier descriptors — url,
title, H1/H2 headings, first-500-char excerpt. Plain parsing, no LLM.
``fields=content``: full untruncated page text for the extractors, from
the same source the legacy extraction path reads (parity contract).
NOTE: ``fields=content`` has NO consumer in the pipeline any more. It existed
only to ship page text to the engine's extraction activities; those now run
inside sor (``/classify``, ``/extract-domain``, …) and read the pages
straight from the repository. Kept for ad-hoc inspection pending a decision
on removing it.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `fields` | query | string | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_Union_PageDescriptorsData__PageContentsData__
- `request_id` · string · required
- `success` · true
- `data` · PageDescriptorsData | PageContentsData · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/crawl-attempts/{attempt_id}/pages
Ingest Pages
Operation id: `ingest_pages_api_v1_internal_workflow_engine_onboarding_crawl_attempts__attempt_id__pages_post`
Checkpointed batch of crawled pages. Idempotent via the
(crawl_attempt_id, normalized_url) unique constraint.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `attempt_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): IngestPagesBody
- `pages` · IngestPageItem[]
- `url` · string · required
- `normalized_url` · string · required
- `title` · string | null
- `http_status` · integer | null
- `content_type` · string | null
- `depth` · integer
- `markdown` · string | null
- `cleaned_html` · string | null
- `raw_text` · string | null
- `metadata_json` · object | null
- `links_json` · object | null
- `images_json` · object[] | null
- `crawl_error` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_IngestPagesData_
- `request_id` · string · required
- `success` · true
- `data` · IngestPagesData · required
- `created` · integer
- `updated` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/internal/workflow-engine/onboarding/photos/tags
Update Photo Tags
Operation id: `update_photo_tags_api_v1_internal_workflow_engine_onboarding_photos_tags_patch`
Merge vision categories into ``photo_metadata.tags`` (S5 / design §13 [3]).
Set-union per photo — tags an admin or an earlier pass added are never
dropped. Unknown (or deleted) photo_ids are skipped and reported rather
than 404-ing the batch: the categoriser's write must land for every photo
that still exists.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PhotoTagsBody
- `updates` · PhotoTagUpdateItem[]
- `photo_id` · string (uuid) · required
- `tags` · string[]
**Responses**
- `200` Successful Response: SuccessResponse_PhotoTagsData_
- `request_id` · string · required
- `success` · true
- `data` · PhotoTagsData · required
- `updated` · integer
- `skipped` · string[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/assemble
Assemble Run Payload
Operation id: `assemble_run_payload_api_v1_internal_workflow_engine_onboarding_runs__run_id__assemble_post`
Assemble every fan-out fragment into one payload and merge it (W6).
Concatenation only — dedup is merge planning's job. Routes through the same
``apply_assembled_merge`` as ``/runs/{run_id}/merge`` so the fan-out never
grows a second promotion path; provenance is stamped ``website scrape
fanout``. Response data is the merge counts summary.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): AssemblePayloadBody
- `fragments` · object[]
- `contexts` · object[]
- `extract_attempt_id` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/categorise-images
Categorise Run Images
Operation id: `categorise_run_images_api_v1_internal_workflow_engine_onboarding_runs__run_id__categorise_images_post`
Categorise the run's promoted images and auto-assign the logo slots (W7).
One deterministic logo shortlist + one capped multimodal LLM call + two
writes (tags merged, slots filled only-if-empty). The engine is a thin
orchestrator over this call: all the vision logic and every guardrail — the
shortlist-only logo pick, the >=180px favicon floor, the never-clobber slot
rule — live in sor, next to the photo data they judge.
Takes no body. Same 200-with-``status`` contract as ``/stages/{stage}``: a
run with no photos comes back ``skipped`` (the LLM is never called) and a
domain failure comes back ``failed`` for the engine to interpret, while
infra faults — and an LLM reply that should simply be re-asked — 5xx so
Temporal retries them.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_CategoriseImagesData_
- `request_id` · string · required
- `success` · true
- `data` · CategoriseImagesData · required: Outcome of the WS3 logo-detection pass (design §13). ``status`` is the pass's own outcome, not the HTTP outcome: "completed", "skipped" (nothing to do — see ``reason``) or "failed" (a domain failure the engine treats per its own fatality policy; infra faults 5xx instead).
- `status` · string
- `reason` · string | null
- `photos` · integer
- `attached` · integer
- `tags_updated` · integer
- `shortlist` · string[]
- `logo` · string | null
- `favicon` · string | null
- `assigned` · object
- `skipped` · object
- `error` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/crawl-attempts
Create Crawl Attempt
Operation id: `create_crawl_attempt_api_v1_internal_workflow_engine_onboarding_runs__run_id__crawl_attempts_post`
Open a crawl attempt for a run. Idempotent by natural key: if the run
already has a *running* attempt (a retried CreateCrawlAttempt activity), it
is returned instead of opening a second one. ``Idempotency-Key`` is accepted
for tracing/forward-compat but the running-attempt check is the guard.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): CreateCrawlAttemptBody
- `crawler_provider` · string
**Responses**
- `200` Successful Response: SuccessResponse_CreateCrawlAttemptData_
- `request_id` · string · required
- `success` · true
- `data` · CreateCrawlAttemptData · required
- `crawl_attempt_id` · string · required
- `reused` · boolean
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/enrich
Enrich Run
Operation id: `enrich_run_api_v1_internal_workflow_engine_onboarding_runs__run_id__enrich_post`
Run WS4 enrichment for one run (design §14) — the whole pass in one call.
Deterministic quality gate over the run's business → (only when it flags
something) one fact-locked LLM call → partial payload through
``apply_assembled_merge`` with ``source_kind: "enrichment"``. When the gate
flags nothing and the FAQ count is already sufficient the LLM is never
called and the response is ``status="skipped"``.
No request body and no idempotency key: the gate re-reads current SOR state
every call, so an already-enriched business is simply no longer thin and a
repeat invocation short-circuits. That is what makes this endpoint safe for
the console to expose as a button later — a fixed per-run key would instead
swallow a deliberate second pass.
Failure split matches ``/stages/{stage}``: a stage-domain failure returns
**200** with ``status="failed"`` so the engine applies its own fatality
policy (enrichment is best-effort), while infra faults — and a malformed LLM
reply, which is worth re-asking — 5xx so Temporal retries.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_EnrichRunData_
- `request_id` · string · required
- `success` · true
- `data` · EnrichRunData · required: What the enrichment pass did — gate verdict first, merge result second. ``status``: ``skipped`` when the gate flagged nothing (the LLM was never called), ``completed`` when the pass ran, ``failed`` for a stage-domain failure the caller owns the policy for (infra faults 5xx instead).
- `status` · string · required
- `flagged` · integer
- `flagged_fields` · string[]
- `reasons` · object
- `faq_gap` · integer
- `llm_called` · boolean
- `enriched` · integer
- `faqs_added` · integer
- `merge` · object | null
- `error` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/merge
Merge Run
Operation id: `merge_run_api_v1_internal_workflow_engine_onboarding_runs__run_id__merge_post`
Merge an engine-assembled extraction payload into the SOR (S4).
The single promotion boundary: the WS2 fan-out assembler and WS4 enrichment
land through the same ``merge_payload`` the legacy chain uses. Typed
``contexts`` list entries (``extra:<kind>``) each become their own
business_context_versions row. Response data is the merge counts summary.
Failures 5xx on purpose (unlike ``/stages/{stage}``): the merge activity's
Temporal retry policy treats transport/5xx as retryable and 4xx as fatal,
which is exactly the split a planner-LLM hiccup vs a bad payload needs.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): MergeRunBody
- `payload` · object · required
- `provenance` · MergeProvenance · required
- `source_kind` · string · required
- `extract_attempt_id` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/photos
List Run Photos
Operation id: `list_run_photos_api_v1_internal_workflow_engine_onboarding_runs__run_id__photos_get`
Read-only compact list of the run's promoted photos (scrape-source,
live) — the same projection the in-sor categoriser scores, exposed for
inspection and for any caller that wants the raw inventory. See
``image_categorisation.run_photo_view`` for the shape.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_RunPhotosData_
- `request_id` · string · required
- `success` · true
- `data` · RunPhotosData · required
- `photos` · RunPhotoData[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/sor-state
Get Sor State
Operation id: `get_sor_state_api_v1_internal_workflow_engine_onboarding_runs__run_id__sor_state_get`
Read-only compact slice of the business's current SOR state for one
extraction domain (design §11), so a WS2/WS4 extractor sees what already
exists and its output dedups well at merge time.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `domain` | query | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SorStateData_
- `request_id` · string · required
- `success` · true
- `data` · SorStateData · required
- `domain` · string · required
- `state` · object
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/stage-status
Record Stage Status
Operation id: `record_stage_status_api_v1_internal_workflow_engine_onboarding_runs__run_id__stage_status_post`
Project a workflow stage transition into ``onboarding_json.stages``.
The admin console's onboarding checklist reads the same shape whether the run
was driven by the legacy in-process chain or the engine; ``executor`` and
``workflow_run_id`` are what distinguish them. The stage key is not validated
against a fixed list so later workstreams (classification, enrichment) can
journal new stages without a sor deploy.
The body is merged over the stage's existing entry, not substituted for it.
``/stages/{stage}`` and ``/stage-status`` are two calls, and keys the wrapper
produced must survive the second one even if the engine doesn't echo them:
``finalize_website_onboarding`` finds the run by the ``website_id`` the
website stage journaled, so dropping it would strand that stage at
``running`` forever.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): StageStatusBody
- `stage` · string · required
- `status` · string · required
- `error` · string | null
- `workflow_run_id` · string | null
- `outcome` · object | null
**Responses**
- `200` Successful Response: SuccessResponse_StageStatusData_
- `request_id` · string · required
- `success` · true
- `data` · StageStatusData · required
- `stage` · string · required
- `status` · string · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/stages/{stage}
Run Stage
Operation id: `run_stage_api_v1_internal_workflow_engine_onboarding_runs__run_id__stages__stage__post`
Run one onboarding stage synchronously.
V1 of the engine-owned pipeline wraps the existing sor stage implementations
rather than reimplementing them, so the legacy chain and the workflow drive
identical code.
A stage-level failure returns **200** with ``status="failed"`` and the error:
the workflow engine owns retry and fatality policy (merge/website are fatal,
company_name/industries are not), so it must see the outcome rather than a
5xx it would blindly retry. Genuine infrastructure faults still 5xx.
Deliberately does not journal — the engine is the single writer of
``onboarding_json.stages`` via ``/stage-status``.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `stage` | path | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): RunStageBody
- `extract_attempt_id` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_RunStageData_
- `request_id` · string · required
- `success` · true
- `data` · RunStageData · required
- `stage` · string · required
- `status` · string · required
- `outcome` · object
- `error` · string | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/onboarding/runs/{run_id}/website-photos/auto-assign
Auto Assign Website Photos
Operation id: `auto_assign_website_photos_api_v1_internal_workflow_engine_onboarding_runs__run_id__website_photos_auto_assign_post`
Fill the logo/favicon ``website_photos`` slots — only where empty.
An admin (or earlier-run) assignment always wins: a filled slot is reported
``already_set`` and never clobbered (design §13 guardrails). The website is
resolved through the run's business. A photo_id that isn't a live photo of
that business is skipped as ``unknown_photo`` so a bad id can't wedge a
slot with a dangling reference.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): AutoAssignWebsitePhotosBody
- `logo_full` · string (uuid) | null
- `favicon` · string (uuid) | null
**Responses**
- `200` Successful Response: SuccessResponse_AutoAssignWebsitePhotosData_
- `request_id` · string · required
- `success` · true
- `data` · AutoAssignWebsitePhotosData · required
- `assigned` · object
- `skipped` · object
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Platform admin invites
1 endpoints. HTML: https://developers.keystone.app/api/internal/platform-admin-invites/
### POST /api/v1/internal/platform-admin-invites/{invite_id}/consume
Consume Platform Admin Invite
Operation id: `consume_platform_admin_invite_api_v1_internal_platform_admin_invites__invite_id__consume_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `invite_id` | path | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): AccountInviteConsumeBody
- `user_id` · string (uuid) · required
- `email` · string (email) · required
- `first_name` · string · required
- `last_name` · string · required
**Responses**
- `200` Successful Response: SuccessResponse_PlatformAdminUserData_
- `request_id` · string · required
- `success` · true
- `data` · PlatformAdminUserData · required
- `id` · string · required
- `kind` · string · required
- `first_name` · string
- `last_name` · string
- `email` · string (email) · required
- `phone` · string | null
- `role` · string · required
- `status` · string · required
- `delivery_status` · string | null
- `joined_at` · string (date-time) · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Portal
1 endpoints. HTML: https://developers.keystone.app/api/internal/internal-portal/
### POST /api/v1/internal/portal/lead_notify
Portal Lead Notify
Operation id: `portal_lead_notify_api_v1_internal_portal_lead_notify_post`
Kairos callback: send one portal lead email with contact details as of now.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PortalLeadNotifyBody
- `business_id` · string (uuid) · required
- `contact_id` · string (uuid) · required
- `lead_phone` · string
**Responses**
- `200` Successful Response: SuccessResponse_PortalLeadNotifyData_
- `request_id` · string · required
- `success` · true
- `data` · PortalLeadNotifyData · required
- `status` · string · required
- `reason` · string | null
- `partial_details` · boolean | null
- `has_name` · boolean | null
- `has_email` · boolean | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Scheduler tick
1 endpoints. HTML: https://developers.keystone.app/api/internal/tick/
### POST /api/v1/internal/tick
Tick
Operation id: `tick_api_v1_internal_tick_post`
Process due auto-schedules.
When ``BLOG_TICK_USE_WORKFLOW`` is set and control plane URL/token are configured,
enqueues ``tpl-blog-generation-e2e`` per due schedule; otherwise generates posts in-process.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_TickResponse_
- `request_id` · string · required
- `success` · true
- `data` · TickResponse · required
- `processed` · integer · required
- `skipped` · integer
- `errors` · integer · required
- `details` · TickResultDetail[]
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Social posts
5 endpoints. HTML: https://developers.keystone.app/api/internal/social-internal/
### POST /api/v1/internal/businesses/{business_id}/social_posts
Create a social post draft (internal)
Operation id: `create_social_post_api_v1_internal_businesses__business_id__social_posts_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostWriteBody
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `target_profile_ids` · string[]
- `target_formats` · SocialPostTargetFormatBody[]
- `social_profile_id` · string · required
- `format` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
- `publish_mode` · "draft" | "publish_now" | "schedule"
- `scheduled_at` · integer | null
- `timezone` · string | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
**Responses**
- `201` Successful Response: SuccessResponse_SocialPostData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostData · required
- `id` · string · required
- `business_id` · string · required
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `media_status` · string | null
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" · required
- `origin` · string
- `scheduled_at` · integer | null
- `timezone` · string | null
- `published_at` · integer | null
- `kairos_schedule_id` · string | null
- `kairos_schedule_status` · string | null
- `engagement` · SocialEngagementData
- `last_engagement_synced_at` · integer | null
- `media` · SocialPostPreviewMediaData[]
- `targets` · SocialPostTargetData[]
- `available_profiles` · SocialProfileData[]
- `has_external_changes` · boolean
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/social_posts/{social_post_id}
Get a social post (internal)
Operation id: `get_social_post_api_v1_internal_businesses__business_id__social_posts__social_post_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostData · required
- `id` · string · required
- `business_id` · string · required
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `media_status` · string | null
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" · required
- `origin` · string
- `scheduled_at` · integer | null
- `timezone` · string | null
- `published_at` · integer | null
- `kairos_schedule_id` · string | null
- `kairos_schedule_status` · string | null
- `engagement` · SocialEngagementData
- `last_engagement_synced_at` · integer | null
- `media` · SocialPostPreviewMediaData[]
- `targets` · SocialPostTargetData[]
- `available_profiles` · SocialProfileData[]
- `has_external_changes` · boolean
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### PATCH /api/v1/internal/businesses/{business_id}/social_posts/{social_post_id}
Update a social post draft (internal)
Operation id: `update_social_post_api_v1_internal_businesses__business_id__social_posts__social_post_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostWriteBody
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `target_profile_ids` · string[]
- `target_formats` · SocialPostTargetFormatBody[]
- `social_profile_id` · string · required
- `format` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
- `publish_mode` · "draft" | "publish_now" | "schedule"
- `scheduled_at` · integer | null
- `timezone` · string | null
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `media_status` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostData · required
- `id` · string · required
- `business_id` · string · required
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `content_markdown` · string · required
- `link_url` · string | null
- `photo_ids` · string[]
- `media_status` · string | null
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" · required
- `origin` · string
- `scheduled_at` · integer | null
- `timezone` · string | null
- `published_at` · integer | null
- `kairos_schedule_id` · string | null
- `kairos_schedule_status` · string | null
- `engagement` · SocialEngagementData
- `last_engagement_synced_at` · integer | null
- `media` · SocialPostPreviewMediaData[]
- `targets` · SocialPostTargetData[]
- `available_profiles` · SocialProfileData[]
- `has_external_changes` · boolean
- `created_at` · integer · required
- `updated_at` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/social_posts/{social_post_id}/publish
Publish a social post immediately (internal)
Operation id: `publish_social_post_api_v1_internal_businesses__business_id__social_posts__social_post_id__publish_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `social_post_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostPublishBody
- `target_profile_ids` · string[]
- `target_formats` · SocialPostTargetFormatBody[]
- `social_profile_id` · string · required
- `format` · "facebook_feed" | "facebook_carousel" | "instagram_feed" | "instagram_carousel" · required
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostPublishResult_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostPublishResult · required
- `social_post` · SocialPostData · required
- `results` · SocialPostPublishTargetResult[] · required
- `success` · boolean · required
- `successful_count` · integer · required
- `failed_count` · integer · required
- `total_count` · integer · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/social_posts/generate
Generate social post caption (internal)
Operation id: `generate_social_post_api_v1_internal_businesses__business_id__social_posts_generate_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `Idempotency-Key` | header | string \| null | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostGenerateBody
- `prompt` · string | null
- `target_profile_ids` · string[]
- `platforms` · ("facebook" | "instagram")[]
- `provider` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostGenerateData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostGenerateData · required
- `content_markdown` · string · required
- `media_briefs` · object[]: Photo briefs for the workflow worker to resolve against the media library, in display order. Empty unless social_media_briefs_enabled is on. The inline (non-workflow) callers return them but never act on them.
- `metadata` · SocialPostGenerateMetadata · required
- `generation_status` · string
- `workflow_run_id` · string | null
- `thread_id` · string | null
- `social_post` · SocialPostData | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Social posts
1 endpoints. HTML: https://developers.keystone.app/api/internal/social-posts/
### POST /api/v1/internal/social_posts/scheduled_publish
Publish Scheduled Social Post
Operation id: `publish_scheduled_social_post_api_v1_internal_social_posts_scheduled_publish_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): SocialPostScheduledPublishBody
- `business_id` · string · required
- `social_post_id` · string · required
- `scheduled_at` · integer · required
**Responses**
- `200` Successful Response: SuccessResponse_SocialPostScheduledPublishData_
- `request_id` · string · required
- `success` · true
- `data` · SocialPostScheduledPublishData · required
- `social_post_id` · string · required
- `status` · "draft" | "queued" | "publishing" | "published" | "failed" | "skipped" · required
- `skipped` · boolean
- `reason` · string | null
- `result` · SocialPostPublishResult | null
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Social workflow engine
3 endpoints. HTML: https://developers.keystone.app/api/internal/social-workflow-engine/
### POST /api/v1/internal/workflow-engine/social/attach-photos
Attach Social Photos
Operation id: `attach_social_photos_api_v1_internal_workflow_engine_social_attach_photos_post`
Attach photos to an existing draft without rewriting it.
The photos are resolved after the draft is stored, and a PUT would demand
the whole form and overwrite whatever it was not told.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolAttachSocialPhotosBody
- `businessId` · string (uuid) · required
- `socialPostId` · string (uuid) · required
- `photoIds` · string (uuid)[] | null
- `mediaStatus` · string | null
- `workflowRunId` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/social/review-media
Review Social Media
Operation id: `review_social_media_api_v1_internal_workflow_engine_social_review_media_post`
Vision-review the photo candidates the worker found in the library.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolReviewSocialMediaBody
- `businessId` · string (uuid) · required
- `mediaBrief` · object | null
- `candidates` · object[] · required
- `workflowRunId` · string | null
- `ranked` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/social/verify-photo
Verify Social Photo
Operation id: `verify_social_photo_api_v1_internal_workflow_engine_social_verify_photo_post`
Does this reframed image work in a feed? One image, one verdict.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): ToolVerifySocialPhotoBody
- `businessId` · string (uuid) · required
- `mediaBrief` · object | null
- `assetId` · string (uuid) · required
- `intent` · object | null
- `workflowRunId` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Website agent attachments
1 endpoints. HTML: https://developers.keystone.app/api/internal/website-agent-attachments/
### GET /api/v1/internal/website-agent/attachments/{kind}/{file_id}/{name}
Get Website Agent Attachment
Operation id: `get_website_agent_attachment_api_v1_internal_website_agent_attachments__kind___file_id___name__get`
One file of the token's request: a document's stored file, or a photo
as WebP sized for the agent to look at.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `kind` | path | string | yes | |
| `file_id` | path | string | yes | |
| `name` | path | string | yes | |
| `x-attachment-token` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Website generation
2 endpoints. HTML: https://developers.keystone.app/api/internal/website-gen/
### POST /api/v1/internal/website-gen/callback
Post Website Gen Callback
Operation id: `post_website_gen_callback_api_v1_internal_website_gen_callback_post`
Record the generator's outcome for a website.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
| `X-Keystone-Run-Token` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteGenCallbackRequest
- `website_id` · string (uuid) · required
- `gen_exit` · integer · required
- `run_id` · string
- `error` · string
- `mode` · string
- `revision` · integer | null
- `checkpoint_sha` · string | null
- `agent_reply` · string | null
- `generator_version` · string
- `prompt_version` · string
- `model_used` · string
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-gen/github-token
Post Website Gen Github Token
Operation id: `post_website_gen_github_token_api_v1_internal_website_gen_github_token_post`
A fresh installation token scoped to the run's one repository, while the
run is live (409 ``RUN_NOT_LIVE`` otherwise). Replaces handing the run the
GitHub App private key (``website_gen_token_vending_enabled``).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Keystone-Run-Token` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteGenGithubTokenRequest
- `access` · "read" | "write" · required
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Website modification agent data
4 endpoints. HTML: https://developers.keystone.app/api/internal/website-mod-agent-data/
### GET /api/v1/internal/website-mod/agent-data/{entity}
List Agent Data
Operation id: `list_agent_data_api_v1_internal_website_mod_agent_data__entity__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entity` | path | string | yes | |
| `X-Keystone-Data-Token` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_Any_
- `request_id` · string · required
- `success` · true
- `data` · any · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/website-mod/agent-data/{entity}/{target_id}
Get Agent Data Record
Operation id: `get_agent_data_record_api_v1_internal_website_mod_agent_data__entity___target_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entity` | path | string | yes | |
| `target_id` | path | string (uuid) | yes | |
| `X-Keystone-Data-Token` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/website-mod/agent-data/schema
Get Agent Data Schema
Operation id: `get_agent_data_schema_api_v1_internal_website_mod_agent_data_schema_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `entity` | query | string | yes | |
| `X-Keystone-Data-Token` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-mod/agent-data/write
Post Agent Data Write
Operation id: `post_agent_data_write_api_v1_internal_website_mod_agent_data_write_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Keystone-Data-Token` | header | string \| null | no | |
**Request body** (application/json, required): AgentDataWriteBody
- `entity` · string · required
- `op` · "create" | "update" · required
- `target_id` · string (uuid) | null
- `fields` · object
- `idempotency_key` · string · required
- `summary` · string
- `allow_duplicate` · boolean
**Responses**
- `200` Successful Response: SuccessResponse_AgentDataWriteData_
- `request_id` · string · required
- `success` · true
- `data` · AgentDataWriteData · required
- `entity` · string · required
- `op` · string · required
- `target_id` · string · required
- `label` · string · required
- `replayed` · boolean
- `field_changes` · AgentDataFieldChange[]
- `record` · object
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Website modifications
17 endpoints. HTML: https://developers.keystone.app/api/internal/website-mod/
### GET /api/v1/internal/businesses/{business_id}/website-mod/check-gate
Get Website Mod Check Gate
Operation id: `get_website_mod_check_gate_api_v1_internal_businesses__business_id__website_mod_check_gate_get`
``{blocked, status, problems, message}``: whether the pending change's
browser check holds it back (the inbox router's guard on a chat "yes").
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/website-mod/context
Get Website Mod Context
Operation id: `get_website_mod_context_api_v1_internal_businesses__business_id__website_mod_context_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `verify_repo` | query | boolean | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/v1/internal/businesses/{business_id}/website-mod/pending-changes
Get Pending Website Mod Changes
Operation id: `get_pending_website_mod_changes_api_v1_internal_businesses__business_id__website_mod_pending_changes_get`
List console-safe pending website-mod changes for a business (no GitHub PR fields).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/businesses/{business_id}/website-mod/requirements/texts
Post Website Mod Requirement Texts
Operation id: `post_website_mod_requirement_texts_api_v1_internal_businesses__business_id__website_mod_requirements_texts_post`
Return decoded requirement document text for router clarify / plan hydration.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `business_id` | path | string (uuid) | yes | |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): RequirementTextsRequest
- `file_ids` · string[]
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-mod/agent-callback
Post Website Mod Agent Callback
Operation id: `post_website_mod_agent_callback_api_v1_internal_website_mod_agent_callback_post`
Store agent job outcome for SOR plan polling (Redis, keyed by plan_request_id).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteModAgentCallbackRequest
- `plan_request_id` · string · required
- `website_id` · string | string (uuid) · required
- `status` · string · required
- `summary` · string
- `agent_message` · string
- `file_updates` · object[]
- `primitives` · object[]
- `error` · string
- `cost_usd` · number | null
- `duration_s` · number | null
- `run_id` · string
- `smoke` · object | null
- `confirmation` · object | null
- `denied_commands` · string[]
- `stop_source` · string
- `model` · string
- `usage` · object | null
- `browser_check` · object | null
- `check_only` · boolean
- `interrupt` · object | null
- `notes_delivered` · string[]
- `fetched_urls` · object[]
- `notes_pending` · string[]
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-mod/agent-progress
Post Website Mod Agent Progress
Operation id: `post_website_mod_agent_progress_api_v1_internal_website_mod_agent_progress_post`
Append a sanitized progress event for the live console activity feed.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteModAgentProgressRequest
- `plan_request_id` · string · required
- `website_id` · string | string (uuid) · required
- `seq` · integer · required
- `ts` · integer | null
- `kind` · string · required
- `stage` · string | null
- `text` · string | null
- `tool` · string | null
- `label` · string | null
- `block` · string | null
- `status` · string | null
- `partial` · boolean
- `artifact` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-mod/check-artifacts
Post Website Mod Check Artifact
Operation id: `post_website_mod_check_artifact_api_v1_internal_website_mod_check_artifacts_post`
One browser-check screenshot from a session pod, while its run is going (or its "Check again" run is marked). The image is the raw request body.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `plan_request_id` | query | string | yes | |
| `route` | query | string | yes | |
| `viewport` | query | string | yes | |
| `kind` | query | string | no | |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-mod/deploy-reconcile
Post Website Mod Deploy Reconcile
Operation id: `post_website_mod_deploy_reconcile_api_v1_internal_website_mod_deploy_reconcile_post`
Kairos HTTP callback: poll pending website-mod deploys and self-reschedule.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/cancel-pending
Post Cancel Pending Website Mod
Operation id: `post_cancel_pending_website_mod_api_v1_internal_workflow_engine_website_mod_cancel_pending_post`
Clear Redis pending-change + release repo lock after reject/cancel.
``tombstone_run``: the engine, giving up on a plan, knows the run is over.
The run's cancelled tombstone is laid FIRST — it is what stops a sor plan
handler still executing past the engine's hangup from registering a
change nobody can publish (it answers the cancelled terminal at its late
check instead; prod KS-2485) — then the usual scoped cleanup runs.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/deploy-status
Post Deploy Status
Operation id: `post_deploy_status_api_v1_internal_workflow_engine_website_mod_deploy_status_post`
Poll GitHub Actions deploy.yml status for a pending website-mod production deploy.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/gate-status
Post Website Mod Gate Status
Operation id: `post_website_mod_gate_status_api_v1_internal_workflow_engine_website_mod_gate_status_post`
Read-mostly gate probe (Redis + open Keystone PRs + lock) after GH reconcile.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/merge-deploy
Post Merge Deploy
Operation id: `post_merge_deploy_api_v1_internal_workflow_engine_website_mod_merge_deploy_post`
Merge an approved PR and best-effort trigger ``deploy.yml``.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/plan-and-open-pr
Post Plan And Open Pr
Operation id: `post_plan_and_open_pr_api_v1_internal_workflow_engine_website_mod_plan_and_open_pr_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PlanAndOpenPrRequest
- `business_id` · string | string (uuid) · required
- `workflow_run_id` · string
- `thread_id` · string
- `extracted_data` · object | null
- `force_repo_map_refresh` · boolean
- `plan_modes` · string[] | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/preview-status
Post Preview Status
Operation id: `post_preview_status_api_v1_internal_workflow_engine_website_mod_preview_status_post`
Poll for the Cloudflare version preview URL of a website-mod PR branch.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/reconcile-pending
Post Website Mod Reconcile Pending
Operation id: `post_website_mod_reconcile_pending_api_v1_internal_workflow_engine_website_mod_reconcile_pending_post`
Align Redis + repo_locks with open Keystone website-mod PRs (GitHub = SoT).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/repo-lock/acquire
Post Acquire Repo Lock
Operation id: `post_acquire_repo_lock_api_v1_internal_workflow_engine_website_mod_repo_lock_acquire_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-mod/repo-lock/release
Post Release Repo Lock
Operation id: `post_release_repo_lock_api_v1_internal_workflow_engine_website_mod_repo_lock_release_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): object
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Website preview
1 endpoints. HTML: https://developers.keystone.app/api/internal/website-preview/
### POST /api/v1/internal/website-previews/reap
Reap website previews: fail stuck revisions, expire overdue drafts
Operation id: `reap_website_previews_api_v1_internal_website_previews_reap_post`
Sweep counts: ``revisions_failed`` (stuck past the reaper's clock),
``previews_expired`` (ready|failed past their +30d ``expires_at``, 0 while
expiry is off), and the lost starts ``starts_resent``,
``revisions_never_started`` and ``builds_never_started``.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_WebsitePreviewReapResponse_
- `request_id` · string · required
- `success` · true
- `data` · WebsitePreviewReapResponse · required: Internal reap sweep counts (mark-only; V1 keeps resources).
- `revisions_failed` · integer · required
- `previews_expired` · integer · required
- `starts_resent` · integer
- `revisions_never_started` · integer
- `builds_never_started` · integer
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Internal API: Website preview runs
7 endpoints. HTML: https://developers.keystone.app/api/internal/website-preview-runs/
### POST /api/v1/internal/website-preview/agent-callback
Post Website Preview Agent Callback
Operation id: `post_website_preview_agent_callback_api_v1_internal_website_preview_agent_callback_post`
Store an edit slot's result (2 h) for the workflow's settle. Only an
edit run's token posts here: a build reports through the generator
callback, and a stored result is settled as a slot's. A check-only run
("Check again") settles nothing: its check is recorded here.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
| `X-Keystone-Run-Token` | header | string \| null | no | |
**Request body** (application/json, required): PreviewAgentCallbackRequest
- `plan_request_id` · string · required
- `website_id` · string | string (uuid) · required
- `status` · string · required
- `summary` · string
- `agent_message` · string
- `file_updates` · object[]
- `primitives` · object[]
- `error` · string
- `cost_usd` · number | null
- `duration_s` · number | null
- `run_id` · string
- `smoke` · object | null
- `confirmation` · object | null
- `denied_commands` · string[]
- `stop_source` · string
- `model` · string
- `usage` · object | null
- `browser_check` · object | null
- `check_only` · boolean
- `interrupt` · object | null
- `notes_delivered` · string[]
- `fetched_urls` · object[]
- `notes_pending` · string[]
- `checks` · object | null
- `failure_kind` · string
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-preview/agent-progress
Post Website Preview Agent Progress
Operation id: `post_website_preview_agent_progress_api_v1_internal_website_preview_agent_progress_post`
Append a sanitized event to the run's activity trail. A full build's
ring is deeper than an edit's: it runs for most of an hour.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
| `X-Keystone-Run-Token` | header | string \| null | no | |
**Request body** (application/json, required): WebsiteModAgentProgressRequest
- `plan_request_id` · string · required
- `website_id` · string | string (uuid) · required
- `seq` · integer · required
- `ts` · integer | null
- `kind` · string · required
- `stage` · string | null
- `text` · string | null
- `tool` · string | null
- `label` · string | null
- `block` · string | null
- `status` · string | null
- `partial` · boolean
- `artifact` · string | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/website-preview/check-artifacts
Post Website Preview Check Artifact
Operation id: `post_website_preview_check_artifact_api_v1_internal_website_preview_check_artifacts_post`
One browser-check screenshot from a preview edit's pod, under that run's token. The image is the raw request body.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `plan_request_id` | query | string | yes | |
| `route` | query | string | yes | |
| `viewport` | query | string | yes | |
| `kind` | query | string | no | |
| `x-internal-api-key` | header | string \| null | no | |
| `X-Keystone-Run-Token` | header | string \| null | no | |
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-preview/runs/fail
Post Preview Run Fail
Operation id: `post_preview_run_fail_api_v1_internal_workflow_engine_website_preview_runs_fail_post`
``{state}`` — ``failed``, or the state a settled run already had.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PreviewRunFailBody
- `preview_id` · string (uuid) · required
- `revision` · integer
- `kind` · "preview" | "edit" | "full" · required
- `run_id` · string
- `reason` · string
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-preview/runs/settle
Post Preview Run Settle
Operation id: `post_preview_run_settle_api_v1_internal_workflow_engine_website_preview_runs_settle_post`
``{state, checkpoint_sha, reason, fallback_eligible}``; idempotent.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PreviewRunRefBody
- `preview_id` · string (uuid) · required
- `revision` · integer
- `kind` · "preview" | "edit" | "full" · required
- `run_id` · string
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-preview/runs/start
Post Preview Run Start
Operation id: `post_preview_run_start_api_v1_internal_workflow_engine_website_preview_runs_start_post`
``{executor, run_id, deadline_s, state}``; idempotent per attempt.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PreviewRunStartBody
- `preview_id` · string (uuid) · required
- `revision` · integer
- `kind` · "preview" | "edit" | "full" · required
- `workflow_run_id` · string
- `attempt` · integer
- `force_executor` · "job" | "session" | "build_pod" | null
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/internal/workflow-engine/website-preview/runs/status
Post Preview Run Status
Operation id: `post_preview_run_status_api_v1_internal_workflow_engine_website_preview_runs_status_post`
``{state, executor, reason, fallback_eligible, deadline_s}``. Read-only
but for one case: a later attempt whose launch was lost is failed here (no
one else will), hence the commit.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `x-internal-api-key` | header | string \| null | no | |
**Request body** (application/json, required): PreviewRunRefBody
- `preview_id` · string (uuid) · required
- `revision` · integer
- `kind` · "preview" | "edit" | "full" · required
- `run_id` · string
**Responses**
- `200` Successful Response: SuccessResponse_dict_str__Any__
- `request_id` · string · required
- `success` · true
- `data` · object · required
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
# Webhooks and callbacks
Inbound calls from GitHub, Entri, Meta, and the voice provider.
Endpoints third parties call into. Each verifies the provider's own signature header rather than a Keystone credential.
- Audience: Third-party providers
- Base URL: https://sor.keystone.app
- Authentication: Provider signature. GitHub: `X-Hub-Signature-256`. Entri: `Entri-Signature-V3` with `Entri-Timestamp`. Meta and the voice provider verify their own signatures.
- Endpoints: 7 in 4 groups
- HTML: https://developers.keystone.app/api/webhooks/
## Webhooks and callbacks: Ads platform webhooks
2 endpoints. HTML: https://developers.keystone.app/api/webhooks/webhook/
### GET /api/v1/ads/webhook/{platform}
Webhook subscription handshake (returns hub.challenge)
Operation id: `webhook_verify_api_v1_ads_webhook__platform__get`
Meta calls this once when the subscription is created — must
echo back ``hub.challenge`` if our verify-token matches.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `platform` | path | string | yes | |
| `hub.mode` | query | string | yes | |
| `hub.challenge` | query | string | yes | |
| `hub.verify_token` | query | string | yes | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/ads/webhook/{platform}
Receive webhook event (signature-verified inline; ack 200 fast)
Operation id: `webhook_receive_api_v1_ads_webhook__platform__post`
Verify signature → dispatch by topic → ack.
Verification uses the raw request body — read it before parsing to
avoid HMAC mismatch from re-encoding. Failures return 401 silently;
Meta retries automatically.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `platform` | path | string | yes | |
| `X-Hub-Signature-256` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Webhooks and callbacks: Entri
1 endpoints. HTML: https://developers.keystone.app/api/webhooks/entri/
### POST /api/v1/webhooks/entri
Entri DNS-automation webhook (propagation status)
Operation id: `entri_webhook_api_v1_webhooks_entri_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Entri-Signature-V3` | header | string \| null | no | |
| `Entri-Timestamp` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Webhooks and callbacks: Github
1 endpoints. HTML: https://developers.keystone.app/api/webhooks/github/
### POST /api/v1/webhooks/github
Customer-site repo webhook (sitemap refresh + deploy completion)
Operation id: `github_webhook_api_v1_webhooks_github_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Hub-Signature-256` | header | string \| null | no | |
| `X-GitHub-Event` | header | string \| null | no | |
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## Webhooks and callbacks: Voice (Retell)
3 endpoints. HTML: https://developers.keystone.app/api/webhooks/retell/
### POST /api/v1/voice/retell/events
Retell Call Events
Operation id: `retell_call_events_api_v1_voice_retell_events_post`
Retell call-lifecycle webhook: every ended/analyzed call becomes a
contact + call record (see ingest.py for the notification posture).
``call_ended`` writes the base record; ``call_analyzed`` upserts the
summary and the post-call capture onto the same row (idempotent by
provider call id). The capture (design doc P22) prefers the post-call
analysis fields (``call_analysis.custom_analysis_data`` — whole-transcript
extraction) and falls back to what the mid-call extract tool collected
(``collected_dynamic_variables``); both are only present on
``call_analyzed``. Always 200 — Retell retries 5xxs and duplicates are
wasted work.
**Responses**
- `200` Successful Response: object
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/voice/retell/inbound
Retell Inbound Webhook
Operation id: `retell_inbound_webhook_api_v1_voice_retell_inbound_post`
Resolve dialed number → business, inject its context as dynamic variables.
**Request body** (application/json, required): InboundWebhookRequest
- `event` · string · required
- `call_inbound` · InboundCallPayload | null
- `from_number` · string | null
- `to_number` · string | null
- `agent_id` · string | null
- `custom_sip_headers` · object | null
**Responses**
- `200` Successful Response: InboundWebhookResponse
- `call_inbound` · InboundCallResponse: Fields Retell accepts back for a call (all optional). ``reject=True`` declines the call (S2: numbers Keystone doesn't know / has released); override_agent_id is not used — every number binds the shared agent.
- `reject` · boolean | null
- `dynamic_variables` · object | null
- `metadata` · object | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### POST /api/v1/voice/retell/tool
Retell Tool Webhook
Operation id: `retell_tool_webhook_api_v1_voice_retell_tool_post`
Retell custom-function endpoint: dispatch mid-call tool calls.
Retell POSTs ``{"name": ..., "args": {...}, "call": {...}}`` signed with the
same ``x-retell-signature`` scheme as the inbound webhook (same key). Tenant
identity comes from ``call.metadata.business_id`` — the value OUR inbound
webhook planted, which is why it's trustworthy (design doc §6.4). The
response body is a plain JSON string: Retell hands it to the agent's LLM
verbatim, so failures are honest instruction strings, never 5xx.
**Responses**
- `200` Successful Response: any
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
# System
Health checks and site registration.
Unauthenticated endpoints: liveness and readiness probes, and the sites registry lookup.
- Audience: Keystone services
- Base URL: https://sor.keystone.app
- Authentication: No authentication
- Endpoints: 4 in 3 groups
- HTML: https://developers.keystone.app/api/system/
## System: Health
2 endpoints. HTML: https://developers.keystone.app/api/system/api/
### GET /api/health
Health
Operation id: `health_api_health_get`
Service health check. Verifies DB connection before returning success.
**Responses**
- `200` Successful Response: HealthResponse
- `request_id` · string · required
- `success` · true
- `data` · HealthData · required: Typed data for /api/health success response.
- `status` · string
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
### GET /api/health/redis
Health Redis
Operation id: `health_redis_api_health_redis_get`
Redis health check. Verifies Redis connection before returning success.
**Responses**
- `200` Successful Response: HealthResponse
- `request_id` · string · required
- `success` · true
- `data` · HealthData · required: Typed data for /api/health success response.
- `status` · string
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## System: Health
1 endpoints. HTML: https://developers.keystone.app/api/system/health/
### GET /health
Health Root
Operation id: `health_root_health_get`
Same behavior as ``GET /api/health``; path often used by load balancers and probes.
**Responses**
- `200` Successful Response: HealthResponse
- `request_id` · string · required
- `success` · true
- `data` · HealthData · required: Typed data for /api/health success response.
- `status` · string
- `metadata` · ResponseMetadata | null
- `pagination` · PaginationMetadata | null
- `status_counts` · object | null
- `lifecycle_counts` · LifecycleCounts | null
- `classification_counts` · ClassificationCounts | null
- `meta` · ResponseMeta | null
- `total` · integer | null
- `total_count` · integer | null
- `page` · integer | null
- `per_page` · integer | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
## System: Sites by custom domain
1 endpoints. HTML: https://developers.keystone.app/api/system/by-custom-domain/
### GET /api/v1/sites/by-custom-domain
Get worker name by custom domain
Operation id: `by_custom_domain_api_v1_sites_by_custom_domain_get`
Public endpoint: map custom_domain to worker_name (website.repo_name) for routing. No auth. Returns 404 if not found.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `domain` | query | string | yes | Custom domain (hostname) to look up |
**Responses**
- `200` Successful Response: ByCustomDomainResponse
- `worker_name` · string · required
- `redirect_to` · string | null
- `400` Bad request: ErrorResponse
- `401` Unauthorized: ErrorResponse
- `403` Forbidden: ErrorResponse
- `404` Not found: ErrorResponse
- `422` Validation error: ErrorResponse
- `500` Internal server error: ErrorResponse
- `503` Service unavailable: ErrorResponse
# Auth service
Sign-up, login, tokens, OAuth, users, roles, and permissions (Heimdal).
Heimdal issues and verifies every token the other surfaces accept: console user tokens, consumer (website visitor) tokens, and MCP access tokens via OAuth. It also holds users, roles, and permissions. Operations tagged `internal` are service-to-service.
- Audience: Console and first-party apps
- Base URL: https://auth.keystone.app
- Authentication: Varies by endpoint. Sign-in endpoints take credentials; everything else takes `Authorization: Bearer <token>`. Endpoints tagged `internal` take `X-Internal-Api-Key`.
- Endpoints: 45 in 9 groups
- HTML: https://developers.keystone.app/api/auth/
## Auth service: Auth
10 endpoints. HTML: https://developers.keystone.app/api/auth/auth/
### POST /api/v1/auth/forgot-password
Forgot Password
Operation id: `forgot_password_api_v1_auth_forgot_password_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Recaptcha-Token` | header | string \| null | no | |
| `X-Recaptcha-Key-Type` | header | string \| null | no | |
**Request body** (application/json, required): ForgotPasswordRequest
- `email` · string (email) · required
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/login
Login
Operation id: `login_api_v1_auth_login_post`
OAuth2 compatible token login, get an access token for future requests.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Recaptcha-Token` | header | string \| null | no | |
| `X-Recaptcha-Key-Type` | header | string \| null | no | |
**Request body** (application/x-www-form-urlencoded, required): Body_login_api_v1_auth_login_post
- `grant_type` · string | null
- `username` · string · required
- `password` · string (password) · required
- `scope` · string
- `client_id` · string | null
- `client_secret` · string | null
**Responses**
- `200` Successful Response: Token
- `access_token` · string · required
- `token_type` · string · required
- `refresh_token` · string · required
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/logout
Logout
Operation id: `logout_api_v1_auth_logout_post`
**Request body** (application/json, required): LogoutRequest
- `refresh_token` · string · required
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/refresh
Refresh Token
Operation id: `refresh_token_api_v1_auth_refresh_post`
Rotate refresh token.
**Request body** (application/json, required): RefreshTokenRequest
- `refresh_token` · string · required
**Responses**
- `200` Successful Response: Token
- `access_token` · string · required
- `token_type` · string · required
- `refresh_token` · string · required
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/resend-verification
Resend Verification
Operation id: `resend_verification_api_v1_auth_resend_verification_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Recaptcha-Token` | header | string \| null | no | |
| `X-Recaptcha-Key-Type` | header | string \| null | no | |
**Request body** (application/json, required): ResendVerificationRequest
- `email` · string · required
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/reset-password
Reset Password
Operation id: `reset_password_api_v1_auth_reset_password_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Recaptcha-Token` | header | string \| null | no | |
| `X-Recaptcha-Key-Type` | header | string \| null | no | |
**Request body** (application/json, required): ResetPasswordRequest
- `token` · string | null
- `email` · string (email) | null
- `otp` · string | null
- `new_password` · string · required
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/signup
Create User
Operation id: `create_user_api_v1_auth_signup_post`
Create new user (201), or resend the code to a pending one (200).
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Recaptcha-Token` | header | string \| null | no | |
| `X-Recaptcha-Key-Type` | header | string \| null | no | |
**Request body** (application/json, required): UserCreate
- `email` · string (email) · required
- `first_name` · string · required
- `last_name` · string · required
- `is_active` · boolean | null
- `password` · string · required
- `invite_id` · string | null
**Responses**
- `201` Successful Response: UserResponse
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `is_active` · boolean | null
- `id` · string (uuid) · required
- `role_id` · integer · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `avatar_url` · string | null
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/social
Social Login
Operation id: `social_login_api_v1_auth_social_post`
Social Login (Google).
Verifies ID Token, finds/creates user, issues JWTs.
**Request body** (application/json, required): SocialLoginRequest
- `provider` · string · required
- `token` · string · required
**Responses**
- `200` Successful Response: Token
- `access_token` · string · required
- `token_type` · string · required
- `refresh_token` · string · required
- `422` Validation Error: HTTPValidationError
### GET /api/v1/auth/verify-email
Verify Email Token
Operation id: `verify_email_token_api_v1_auth_verify_email_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `token` | query | string | yes | |
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/auth/verify-email
Verify Email Otp
Operation id: `verify_email_otp_api_v1_auth_verify_email_post`
Verify by the emailed six-digit code and sign the user in. 409 ALREADY_VERIFIED for a
verified account, 401 ACCOUNT_INACTIVE for an inactive one, 400 INVALID_OTP otherwise.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Recaptcha-Token` | header | string \| null | no | |
| `X-Recaptcha-Key-Type` | header | string \| null | no | |
**Request body** (application/json, required): VerifyEmailOtpRequest
- `email` · string (email) · required
- `otp` · string · required
**Responses**
- `200` Successful Response: Token
- `access_token` · string · required
- `token_type` · string · required
- `refresh_token` · string · required
- `422` Validation Error: HTTPValidationError
## Auth service: Consumer
4 endpoints. HTML: https://developers.keystone.app/api/auth/consumer/
### POST /api/v1/consumer/auth/passwordless_auth
Passwordless Auth
Operation id: `passwordless_auth_api_v1_consumer_auth_passwordless_auth_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string | yes | |
**Request body** (application/json, required): PasswordlessAuthBody
- `phone` · string · required
- `verification_token` · string · required
- `first_name` · string · required
- `last_name` · string · required
- `email` · string · required
- `webchat_session_id` · string | null
**Responses**
- `201` Successful Response: object
- `422` Validation Error: HTTPValidationError
### POST /api/v1/consumer/auth/send_code
Send Code
Operation id: `send_code_api_v1_consumer_auth_send_code_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string | yes | |
**Request body** (application/json, required): SendCodeBody
- `phone` · string · required
**Responses**
- `200` Successful Response: object
- `422` Validation Error: HTTPValidationError
### POST /api/v1/consumer/auth/verify_code
Verify Code
Operation id: `verify_code_api_v1_consumer_auth_verify_code_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string | yes | |
**Request body** (application/json, required): VerifyCodeBody
- `phone` · string · required
- `code` · string · required
**Responses**
- `200` Successful Response: object
- `422` Validation Error: HTTPValidationError
### GET /api/v1/consumer/me
Consumer Me
Operation id: `consumer_me_api_v1_consumer_me_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-API-Key` | header | string \| null | no | |
| `authorization` | header | string \| null | no | |
**Responses**
- `200` Successful Response: object
- `422` Validation Error: HTTPValidationError
## Auth service: Internal
9 endpoints. HTML: https://developers.keystone.app/api/auth/internal/
### POST /api/v1/internal/account-invites/send-email
Send Account Invite Email
Operation id: `send_account_invite_email_api_v1_internal_account_invites_send_email_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalAccountInviteEmailRequest
- `email` · string (email) · required
- `business_name` · string · required
- `invite_id` · string · required
- `owner_name` · string
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/internal/billing-notifications/send-email
Send Billing Notification Email
Operation id: `send_billing_notification_email_api_v1_internal_billing_notifications_send_email_post`
Email one business user about a billing event (DF-10). Called per recipient by sor, which owns
the business↔user mapping; this service only knows how to render and send.
**422 on a kind we have no copy for.** The alternative — generic wording — produces an email that
names no amount, no date and no reason, which spends the customer's one notification on nothing
and reports nothing to us. The caller retries and dead-letters it, so it surfaces as a tracked
failure. `notification_data` gets no such treatment: it is best-effort, and a missing or
unreadable value costs a line in the callout rather than the email.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalBillingNotificationEmailRequest
- `email` · string (email) · required
- `business_name` · string · required
- `kind` · string · required
- `billing_url` · string (uri) | null
- `notification_data` · object
- `event_id` · string
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/internal/escalation-notifications/send-email
Send Escalation Notification Email
Operation id: `send_escalation_notification_email_api_v1_internal_escalation_notifications_send_email_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalEscalationNotificationEmailRequest
- `email` · string (email) · required
- `business_name` · string · required
- `escalation_reason` · string
- `consumer_label` · string
- `matched_keyword` · string
- `last_message_preview` · string
- `conversation_url` · string (uri) | null
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/internal/lead-notifications/send-email
Send Lead Notification Email
Operation id: `send_lead_notification_email_api_v1_internal_lead_notifications_send_email_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalLeadNotificationEmailRequest
- `email` · string (email) · required
- `business_name` · string · required
- `lead_name` · string
- `lead_email` · string
- `lead_phone` · string
- `lead_message` · string
- `contact_url` · string (uri) | null
- `kind` · string
- `source` · string
- `lead_fields` · LeadFieldItem[]
- `label` · string · required
- `value` · string · required
- `partial_details` · boolean
- `ai_pursuing` · boolean
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### POST /api/v1/internal/review-notifications/send-email
Send Review Notification Email
Operation id: `send_review_notification_email_api_v1_internal_review_notifications_send_email_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalReviewNotificationEmailRequest
- `email` · string (email) · required
- `business_name` · string · required
- `listing_title` · string
- `reviewer_name` · string
- `rating` · integer | null
- `review_body` · string
- `review_url` · string (uri) | null
**Responses**
- `200` Successful Response: any
- `422` Validation Error: HTTPValidationError
### GET /api/v1/internal/users
List Users
Operation id: `list_users_api_v1_internal_users_get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `role_id` | query | integer | yes | Filter by Heimdal role_id |
| `limit` | query | integer | no | |
| `offset` | query | integer | no | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Responses**
- `200` Successful Response: InternalUserListResponse
- `users` · InternalUserRecord[] · required
- `user_id` · string (uuid) · required
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `phone` · string | null
- `role_id` · integer · required
- `is_active` · boolean · required
- `email_verified` · boolean · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `422` Validation Error: HTTPValidationError
### PATCH /api/v1/internal/users/{user_id}
Update User
Operation id: `update_user_api_v1_internal_users__user_id__patch`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `user_id` | path | string (uuid) | yes | |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalUserUpdateRequest
- `first_name` · string | null
- `last_name` · string | null
- `phone` · string | null
- `email` · string (email) | null
- `role_id` · integer | null
- `is_active` · boolean | null
**Responses**
- `200` Successful Response: InternalUserRecord
- `user_id` · string (uuid) · required
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `phone` · string | null
- `role_id` · integer · required
- `is_active` · boolean · required
- `email_verified` · boolean · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `422` Validation Error: HTTPValidationError
### POST /api/v1/internal/users/lookup
Lookup Users
Operation id: `lookup_users_api_v1_internal_users_lookup_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalUserLookupRequest
- `user_ids` · string (uuid)[]
- `emails` · string (email)[]
**Responses**
- `200` Successful Response: InternalUserLookupResponse
- `users` · InternalUserRecord[] · required
- `user_id` · string (uuid) · required
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `phone` · string | null
- `role_id` · integer · required
- `is_active` · boolean · required
- `email_verified` · boolean · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `422` Validation Error: HTTPValidationError
### POST /api/v1/internal/users/provision
Provision User
Operation id: `provision_user_api_v1_internal_users_provision_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Internal-Api-Key` | header | string \| null | no | |
**Request body** (application/json, required): InternalProvisionUserRequest
- `email` · string (email) · required
- `first_name` · string · required
- `last_name` · string · required
- `phone` · string | null
- `role_id` · integer
- `send_password_reset` · boolean
**Responses**
- `200` User updated successfully: InternalProvisionUserResponse
- `user_id` · string (uuid) · required
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `phone` · string | null
- `role_id` · integer · required
- `created` · boolean · required
- `password_reset_sent` · boolean · required
- `201` Successful Response: InternalProvisionUserResponse
- `user_id` · string (uuid) · required
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `phone` · string | null
- `role_id` · integer · required
- `created` · boolean · required
- `password_reset_sent` · boolean · required
- `422` Validation Error: HTTPValidationError
## Auth service: OAuth
3 endpoints. HTML: https://developers.keystone.app/api/auth/oauth/
### GET /api/v1/oauth/requests/{request_id}
Get Authorization Request
Operation id: `get_authorization_request_api_v1_oauth_requests__request_id__get`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `request_id` | path | string (uuid) | yes | |
**Responses**
- `200` Successful Response: ConsentRequestInfo
- `id` · string (uuid) · required
- `client_name` · string · required
- `client_host` · string · required
- `scopes` · ScopeInfo[] · required
- `name` · string · required
- `description` · string · required
- `resource` · string · required
- `expires_at` · string (date-time) · required
- `status` · string · required
- `eligible` · boolean · required
- `ineligible_reason` · string | null
- `consented_before` · boolean
- `422` Validation Error: HTTPValidationError
### POST /api/v1/oauth/requests/{request_id}/approve
Approve Authorization Request
Operation id: `approve_authorization_request_api_v1_oauth_requests__request_id__approve_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `request_id` | path | string (uuid) | yes | |
**Responses**
- `200` Successful Response: ConsentDecision
- `redirect_to` · string · required
- `422` Validation Error: HTTPValidationError
### POST /api/v1/oauth/requests/{request_id}/deny
Deny Authorization Request
Operation id: `deny_authorization_request_api_v1_oauth_requests__request_id__deny_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `request_id` | path | string (uuid) | yes | |
**Responses**
- `200` Successful Response: ConsentDecision
- `redirect_to` · string · required
- `422` Validation Error: HTTPValidationError
## Auth service: Permissions
5 endpoints. HTML: https://developers.keystone.app/api/auth/permissions/
### GET /api/v1/permissions/
List Permissions
Operation id: `list_permissions_api_v1_permissions__get`
List all permissions.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `skip` | query | integer | no | |
| `limit` | query | integer | no | |
**Responses**
- `200` Successful Response: PermissionResponse[]
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### POST /api/v1/permissions/
Create Permission
Operation id: `create_permission_api_v1_permissions__post`
Create new permission. Admin only.
**Request body** (application/json, required): PermissionCreate
- `key` · string · required
- `description` · string | null
**Responses**
- `201` Successful Response: PermissionResponse
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### GET /api/v1/permissions/{permission_id}
Get Permission
Operation id: `get_permission_api_v1_permissions__permission_id__get`
Get permission by ID.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `permission_id` | path | integer | yes | |
**Responses**
- `200` Successful Response: PermissionResponse
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### PUT /api/v1/permissions/{permission_id}
Update Permission
Operation id: `update_permission_api_v1_permissions__permission_id__put`
Update permission. Admin only.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `permission_id` | path | integer | yes | |
**Request body** (application/json, required): PermissionUpdate
- `key` · string | null
- `description` · string | null
**Responses**
- `200` Successful Response: PermissionResponse
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### DELETE /api/v1/permissions/{permission_id}
Delete Permission
Operation id: `delete_permission_api_v1_permissions__permission_id__delete`
Delete permission. Admin only.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `permission_id` | path | integer | yes | |
**Responses**
- `204` Successful Response
- `422` Validation Error: HTTPValidationError
## Auth service: Roles
7 endpoints. HTML: https://developers.keystone.app/api/auth/roles/
### GET /api/v1/roles/
List Roles
Operation id: `list_roles_api_v1_roles__get`
List all roles.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `skip` | query | integer | no | |
| `limit` | query | integer | no | |
**Responses**
- `200` Successful Response: RoleResponse[]
- `name` · string · required
- `description` · string | null
- `id` · integer · required
- `permissions` · PermissionResponse[]
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### POST /api/v1/roles/
Create Role
Operation id: `create_role_api_v1_roles__post`
Create new role. Admin only.
**Request body** (application/json, required): RoleCreate
- `name` · string · required
- `description` · string | null
- `id` · integer · required
- `permission_ids` · integer[] | null
**Responses**
- `201` Successful Response: RoleResponse
- `name` · string · required
- `description` · string | null
- `id` · integer · required
- `permissions` · PermissionResponse[]
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### GET /api/v1/roles/{role_id}
Get Role
Operation id: `get_role_api_v1_roles__role_id__get`
Get role by ID.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `role_id` | path | integer | yes | |
**Responses**
- `200` Successful Response: RoleResponse
- `name` · string · required
- `description` · string | null
- `id` · integer · required
- `permissions` · PermissionResponse[]
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### PUT /api/v1/roles/{role_id}
Update Role
Operation id: `update_role_api_v1_roles__role_id__put`
Update role. Admin only.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `role_id` | path | integer | yes | |
**Request body** (application/json, required): RoleUpdate
- `name` · string | null
- `description` · string | null
**Responses**
- `200` Successful Response: RoleResponse
- `name` · string · required
- `description` · string | null
- `id` · integer · required
- `permissions` · PermissionResponse[]
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### DELETE /api/v1/roles/{role_id}
Delete Role
Operation id: `delete_role_api_v1_roles__role_id__delete`
Delete role. Admin only.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `role_id` | path | integer | yes | |
**Responses**
- `204` Successful Response
- `422` Validation Error: HTTPValidationError
### POST /api/v1/roles/{role_id}/permissions
Assign Permissions To Role
Operation id: `assign_permissions_to_role_api_v1_roles__role_id__permissions_post`
Assign permissions to a role. Admin only.
Replaces all existing permissions with the provided list.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `role_id` | path | integer | yes | |
**Request body** (application/json, required): RolePermissionAssign
- `permission_ids` · integer[] · required
**Responses**
- `200` Successful Response: RoleResponse
- `name` · string · required
- `description` · string | null
- `id` · integer · required
- `permissions` · PermissionResponse[]
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
### DELETE /api/v1/roles/{role_id}/permissions/{permission_id}
Remove Permission From Role
Operation id: `remove_permission_from_role_api_v1_roles__role_id__permissions__permission_id__delete`
Remove a specific permission from a role. Admin only.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `role_id` | path | integer | yes | |
| `permission_id` | path | integer | yes | |
**Responses**
- `200` Successful Response: RoleResponse
- `name` · string · required
- `description` · string | null
- `id` · integer · required
- `permissions` · PermissionResponse[]
- `key` · string · required
- `description` · string | null
- `id` · integer · required
- `422` Validation Error: HTTPValidationError
## Auth service: Root
1 endpoints. HTML: https://developers.keystone.app/api/auth/root/
### GET /
Root
Operation id: `root__get`
**Responses**
- `200` Successful Response: any
## Auth service: Users
5 endpoints. HTML: https://developers.keystone.app/api/auth/users/
### PATCH /api/v1/users/{user_id}/role
Update User Role
Operation id: `update_user_role_api_v1_users__user_id__role_patch`
Update another user's role. Restricted to Admin User.
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `user_id` | path | string (uuid) | yes | |
**Request body** (application/json, required): UserRoleUpdate
- `role_id` · integer · required
**Responses**
- `200` Successful Response: UserResponse
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `is_active` · boolean | null
- `id` · string (uuid) · required
- `role_id` · integer · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `avatar_url` · string | null
- `422` Validation Error: HTTPValidationError
### GET /api/v1/users/current_user
Read Current User
Operation id: `read_current_user_api_v1_users_current_user_get`
Get current user.
**Responses**
- `200` Successful Response: UserResponse
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `is_active` · boolean | null
- `id` · string (uuid) · required
- `role_id` · integer · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `avatar_url` · string | null
### PUT /api/v1/users/current_user
Update Current User
Operation id: `update_current_user_api_v1_users_current_user_put`
Update own profile.
**Request body** (application/json, required): UserUpdate
- `first_name` · string | null
- `last_name` · string | null
- `email` · string (email) | null
- `password` · string | null
- `old_password` · string | null
**Responses**
- `200` Successful Response: UserResponse
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `is_active` · boolean | null
- `id` · string (uuid) · required
- `role_id` · integer · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `avatar_url` · string | null
- `422` Validation Error: HTTPValidationError
### POST /api/v1/users/current_user/avatar
Upload Current User Avatar
Operation id: `upload_current_user_avatar_api_v1_users_current_user_avatar_post`
Upload own profile avatar (multipart/form-data).
**Request body** (multipart/form-data, required): Body_upload_current_user_avatar_api_v1_users_current_user_avatar_post
- `file` · string (binary) · required
**Responses**
- `200` Successful Response: UserResponse
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `is_active` · boolean | null
- `id` · string (uuid) · required
- `role_id` · integer · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `avatar_url` · string | null
- `422` Validation Error: HTTPValidationError
### DELETE /api/v1/users/current_user/avatar
Delete Current User Avatar
Operation id: `delete_current_user_avatar_api_v1_users_current_user_avatar_delete`
Remove own profile avatar.
**Responses**
- `200` Successful Response: UserResponse
- `email` · string (email) · required
- `first_name` · string | null
- `last_name` · string | null
- `is_active` · boolean | null
- `id` · string (uuid) · required
- `role_id` · integer · required
- `created_at` · string (date-time) · required
- `updated_at` · string (date-time) · required
- `avatar_url` · string | null
## Auth service: Webhooks
1 endpoints. HTML: https://developers.keystone.app/api/auth/webhooks/
### POST /api/v1/webhooks/sendgrid/events
Sendgrid Events
Operation id: `sendgrid_events_api_v1_webhooks_sendgrid_events_post`
**Parameters**
| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Twilio-Email-Event-Webhook-Signature` | header | string \| null | no | |
| `X-Twilio-Email-Event-Webhook-Timestamp` | header | string \| null | no | |
**Responses**
- `204` Successful Response
- `422` Validation Error: HTTPValidationError
# Keystone MCP server
Endpoint: https://mcp.keystone.app/mcp. Streamable HTTP, stateless. Clients authenticate with an OAuth access token issued by the Auth service; the server forwards every call to the Edits API with that token and its own service key, and holds no data of its own. The model proposes changes; a user reviews each one on a card and applies it with a click the model cannot make. Source: https://github.com/Keystone-PZJR/keystone-mcp
9 tools. HTML: https://developers.keystone.app/mcp/
## Tool: find_business
Find a business. Called by the model, read-only.
Find the business the user wants to work on, by name, website or domain. Shows a picker card: the user clicks the business, which opens an edit session, and its session id comes back to the model. The model cannot open a session or claim a business itself. A business can be edited while the user holds it (a claim, for 24 hours) and its site is not live; one nobody holds they can claim with a click on the card.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | string (2–200) | yes | A business name, website or domain. |
**Returns**
Text listing each match as editable or the reason it is locked, and `structuredContent.view = "picker"` with the matches. `_meta.cardTokens` carries one token per click the card may make (open an editable business, claim an unclaimed one).
**Edits API calls**
- `GET /api/v1/edits/businesses`
## Tool: get_snapshot
Business snapshot. Called by the model, read-only.
Everything about the business in an edit session: the profile in full and every record (locations, services, service items, packages, team, FAQs, job postings, offers) with its id, label and version. Start here, before proposing changes. The user sees a card of what needs work. Hours live on the main location and are read separately so the card can show them.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `session_id` | uuid | yes | The edit session id the user opened (returned when they pick a business on the card). |
**Returns**
The snapshot as JSON text, and `structuredContent.view = "snapshot"` with the business, summary rows for the card, and a console URL.
**Edits API calls**
- `GET /api/v1/edits/sessions/{session_id}/snapshot`
- `GET /api/v1/edits/sessions/{session_id}/records/{entity}/{record_id}`
## Tool: read_business
Read a record. Called by the model, read-only.
One record in full, with its write field names and version: the profile (entity "profile", record_id "profile") or any record get_snapshot lists.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `session_id` | uuid | yes | The edit session id the user opened (returned when they pick a business on the card). |
| `entity` | string | yes | The entity, as get_snapshot names it. |
| `record_id` | string | yes | The record id, or "profile" for the profile. |
**Returns**
The record as JSON text.
**Edits API calls**
- `GET /api/v1/edits/sessions/{session_id}/records/{entity}/{record_id}`
## Tool: propose_changes
Propose changes. Called by the model.
Stage changes to the business for the user to review. Nothing is saved: they see each change on a card that names the business, untick any they do not want, and click Apply. Send every change in one call, up to 50. Include `expected` (the values read) or `version`, so anything changed since is caught. Changes that look like they came from another of the user's businesses start unticked. The call is idempotent on its content within a session.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `session_id` | uuid | yes | The edit session id the user opened (returned when they pick a business on the card). |
| `summary` | string (≤500) | no | One line saying what these changes do. |
| `items` | ChangeItem[] (1–50) | yes | The changes; see ChangeItem below. |
**Returns**
Text naming the staged changeset and each change, with a console link for applying without the card; `structuredContent.view = "changeset"`; `_meta.cardTokens.apply` for the card's Apply click. Changes SOR refuses are listed by position with a reason so the model can fix only those.
**Edits API calls**
- `POST /api/v1/edits/sessions/{session_id}/changesets`
## Tool: discard_changeset
Discard a draft. Called by the model.
Drop a staged changeset the user no longer wants. Nothing was saved from it.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `changeset_id` | uuid | yes | The changeset id propose_changes returned. |
**Returns**
Confirmation text.
**Edits API calls**
- `POST /api/v1/edits/changesets/{changeset_id}/discard`
## Tool: claim_business
Claim this business. Called by the card.
Called by the Keystone card when the user clicks Claim on the business picker. Claims an unclaimed, unlaunched business for the caller for 24 hours so nobody else can take it meanwhile.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `business_id` | uuid | yes | The business id, as find_business lists it. |
| `card_token` | string | yes | The token the card was given for this exact action and target. Minted per click; the model never has one. |
**Returns**
Text with the claim's expiry; `structuredContent.view = "claimed"`; a fresh `open:<business_id>` card token for the next click.
**Edits API calls**
- `PUT /api/v1/edits/businesses/{business_id}/claim`
## Tool: open_session
Work on this business. Called by the card.
Called by the Keystone card when the user picks a business. Binds the chat to that business by opening an edit session; the model then uses the session id for get_snapshot and propose_changes.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `business_id` | uuid | yes | The business id, as find_business lists it. |
| `card_token` | string | yes | The token the card was given for this exact action and target. Minted per click; the model never has one. |
**Returns**
Text naming the business, the session id and its expiry; `structuredContent.view = "session"`.
**Edits API calls**
- `POST /api/v1/edits/sessions`
## Tool: apply_changeset
Apply changes. Called by the card, destructive.
Called by the Keystone card when the user clicks Apply. Applies the ticked items (or exactly the ones given). Each applied change lands in the console's Change History.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `changeset_id` | uuid | yes | The changeset id propose_changes returned. |
| `item_ids` | uuid[] (≤50) | no | Exactly these items; omitted, the ticked ones. |
| `card_token` | string | yes | The token the card was given for this exact action and target. Minted per click; the model never has one. |
**Returns**
Text with a tally (applied, skipped, refused) and each change; `structuredContent.view = "changeset"`; an `undo` card token.
**Edits API calls**
- `POST /api/v1/edits/changesets/{changeset_id}/apply`
## Tool: undo_changeset
Undo changes. Called by the card, destructive.
Called by the Keystone card when the user clicks Undo. Reverts an applied changeset. A value someone changed again since the apply is kept, so an undo can undo nothing; the text says so.
**Input**
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `changeset_id` | uuid | yes | The changeset id propose_changes returned. |
| `card_token` | string | yes | The token the card was given for this exact action and target. Minted per click; the model never has one. |
**Returns**
Text with what was reverted and what was kept; `structuredContent.view = "changeset"`.
**Edits API calls**
- `POST /api/v1/edits/changesets/{changeset_id}/revert`
## ChangeItem
One change inside `propose_changes`. What the model may send is exactly what the Edits API accepts.
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `entity` | string | yes | The entity, as get_snapshot names it: profile, location, service, service_item, package, team_member, faq, job_posting, offer. |
| `op` | "create" \| "update" \| "delete" | yes | What to do. |
| `record_id` | string | no | The record to update or delete; omit for the profile and for creates. |
| `fields` | object | no | New values by write field name; a nested object may be sent in part. |
| `expected` | object | no | The values you read for those fields, so a change made since is caught (compare-and-swap). |
| `version` | string | no | The record version you read, from get_snapshot or read_business. An alternative to expected. |