Keystone Developers
Open Keystone

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.

Open raw file
# 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. |