# Tools and REST API

Use the Content MCP tools through your agent, or call their REST routes from your code. Each tool and route uses the same handler, inputs and outputs.

The caller’s credentials establish identity and product access. Do not pass a product id or acting identity as a tool argument or request-body field.

Full generated schemas are in the [MCP tool catalog](/reference/mcp-tools) and the [Content REST reference](/reference/openapi/content).

## Where to call

Send Content management requests to the platform API with a bearer credential:

| | |
|---|---|
| Base URL | `https://api.lepsto.com` |
| Path | The namespace `/content`, plus the route below — `https://api.lepsto.com/content/posts` |
| Authentication | `Authorization: Bearer <credential>` |

The published [`/openapi.json`](/openapi.json) defines this base URL and bearer authentication for the `/content` routes.

A raw API key is limited to its issued product. A session or exchanged token can select another product its owner belongs to. Before calling, read [The REST API and keys](/interfaces/api-and-keys) for product selection and `X-Product-Id` behavior.

### Posts

| Tool | Read-only | Method | Path | What it is for |
|---|---|---|---|---|
| `content_posts_create` | | `POST` | `/posts` | Create a draft at version 1, optionally with targets |
| `content_posts_update` | | `PATCH` | `/posts/{id}` | Edit text, media or targets; record a version and revoke existing approval |
| `content_posts_get` | yes | `GET` | `/posts/{id}` | Read one post — the way to re-read it mid-run, after a wait or an approval |
| `content_posts_list` | yes | `GET` | `/posts` | List posts newest first; filter by `status` |
| `content_posts_versions` | yes | `GET` | `/posts/{id}/versions` | Every version oldest first — the audit trail |
| `content_posts_submit` | | `POST` | `/posts/{id}/submit` | Submit a draft with targets and required variant fields |
| `content_posts_approve` | | `POST` | `/posts/{id}/approve` | Approve a post awaiting approval, binding to the current version |
| `content_posts_attach_media` | | `POST` | `/posts/{id}/media/attach` | Attach ready media assets, in the given order |
| `content_posts_detach_media` | | `POST` | `/posts/{id}/media/detach` | Remove media assets, keeping the order of the rest |

### Channels

| Tool | Read-only | Method | Path | What it is for |
|---|---|---|---|---|
| `content_channels_list` | yes | `GET` | `/channels` | The channels this product can publish to, with limits and cost |
| `content_channels_get` | yes | `GET` | `/channels/{id}` | One channel's delivery mode and capability descriptor |
| `content_channels_update` | | `PATCH` | `/channels/{id}` | Rename, change delivery mode, or override capability fields |
| `content_channels_bind` | | `POST` | `/channels/{id}/bind` | Bind to a platform connector; clears a pause |
| `content_channels_unbind` | | `POST` | `/channels/{id}/unbind` | Detach the connector; clears a pause, leaves delivery mode alone |
| `content_channels_resume` | | `POST` | `/channels/{id}/resume` | Resume a paused channel once its connection works again |

There is no create tool: channels are seeded, one per network.

### Publications

| Tool | Read-only | Method | Path | What it is for |
|---|---|---|---|---|
| `content_publications_validate` | yes | `POST` | `/publications/validate` | Dry run against up to 20 channels: issues, rendered text and cost |
| `content_publications_schedule` | | `POST` | `/publications` | Queue an approved post for one channel at a time |
| `content_publications_publish` | | `POST` | `/publications/publish` | The same queue, scheduled for this instant |
| `content_publications_list` | yes | `GET` | `/publications` | The queue and the timeline, newest slot first |
| `content_publications_get` | yes | `GET` | `/publications/{id}` | One publication: its state, its slot, where it ended up |
| `content_publications_cancel` | | `POST` | `/publications/{id}/cancel` | Cancel one that has not finished |
| `content_publications_reschedule` | | `POST` | `/publications/{id}/reschedule` | Move it to a new slot |
| `content_publications_confirm` | | `POST` | `/publications/{id}/confirm` | Record that a person published it by hand |
| `content_publications_remind` | | `POST` | `/publications/{id}/remind` | Send the publish reminder again |

### Media

| Tool | Read-only | Method | Path | What it is for |
|---|---|---|---|---|
| `content_media_upload` | | `POST` | `/media` | Ingest media from a public `http(s)` URL |
| `content_media_create_upload_url` | | `POST` | `/media/upload-url` | A signed PUT URL plus a pending asset |
| `content_media_finalize` | | `POST` | `/media/{id}/finalize` | Confirm a signed upload; the asset becomes ready |
| `content_media_get` | yes | `GET` | `/media/{id}` | One asset: storage status, type and size |
| `content_media_list` | yes | `GET` | `/media` | Assets newest first; filter by `status` |

### Metrics

| Tool | Read-only | Method | Path | What it is for |
|---|---|---|---|---|
| `content_metrics_post_series` | yes | `GET` | `/metrics/posts/{id}` | Snapshots of one published post over time |
| `content_metrics_channel_series` | yes | `GET` | `/metrics/channels/{id}` | Daily snapshots of a channel itself |

## Paging

Pass `limit` and `offset` as query parameters to list routes. Posts, channels, publications and media accept `limit` from 1 to 100 (default 50); metric series accept 1 to 500 (default 200). `total` counts all matching records before paging.

## Idempotency

The following five operations accept an `Idempotency-Key` request header over REST. Reuse the key when retrying an unclear outcome to receive the first response instead of repeating the action:

- `content_posts_create` — `POST /posts`
- `content_posts_submit` — `POST /posts/{id}/submit`
- `content_publications_schedule` — `POST /publications`
- `content_publications_publish` — `POST /publications/publish`
- `content_publications_cancel` — `POST /publications/{id}/cancel`

Schedule and publish also accept an `idempotencyKey` field, stored on the publication. Reusing it returns the same publication, except when that publication failed or was cancelled: the key is then spent and returns `409 idempotency_key_spent`. See [Publications](/acquire/content/publications).

## Errors

| Status | When |
|---|---|
| `400` | Invalid input, unusable media, unknown or duplicate target networks, unsupported target types or fields, overlong fields, or replacement `text` sent to schedule, publish or validate |
| `404` | The post, channel, publication or asset does not exist in this product |
| `409` | The action is not allowed from the current state |

A `409` identifies a state or validation conflict. Examples include submitting without targets or required fields, approving a post outside `pending_approval`, editing scheduled or finished content, and acting on a finished publication. Reminding before the first reminder also conflicts.

Publication conflicts include `already_scheduled`, `idempotency_key_spent`, `publication_in_flight`, `publication_changed` and `not_a_reminder_channel`, plus target and variant validation codes. Duplicate and spent-key errors include the affected `publicationId` and `scheduledAt`. Read the record and follow the recovery guidance in [Publications](/acquire/content/publications) before retrying.

## Using these tools from a workflow

Workflows expose the following steps. Approval remains a separate action for a person.

| Step | Notes |
|---|---|
| Create draft post | The head of the chain: another service's event drafts a post |
| Get post, List posts | Re-read an entity mid-run |
| Submit post for approval | The last step on the drafting side |
| Validate post against channels | Check before committing to a slot |
| Schedule publication, Publish now | Both complete on `content/publication.settled` |
| Cancel publication | Returns the cancelled publication directly |
| Read post metrics | The tail of the chain |

Use `content/post.approved` to resume after a person approves the version. `content_posts_approve` is not offered as a workflow step, so a drafting workflow cannot approve its own output.

Schedule and publish return a queued record. Match its `id` to `publicationId` in the later `content/publication.settled` event, then branch on `status` (`published`, `failed` or `cancelled`). Cancellation returns the cancelled record directly.

## Clients

The published `@lepsto/sdk` 1.2.0 has no generated Content client or `@lepsto/sdk/content` import. Its generated namespaces are `mail`, `realtime`, `support`, `tracking` and `users`. Importing the package root does not add Content operations.

Use the MCP tools or call `https://api.lepsto.com/content/…` with an HTTP client and bearer credential. The SDK also exports a generic HTTP client, but no typed Content methods.

Choose retries according to the operation. Use `Idempotency-Key` on the five supported routes, inspect any `Retry-After` header returned, and read the publication after a state conflict before deciding whether to repeat the request.

## Next steps

- [Content REST reference](/reference/openapi/content): the generated schema for every route above.
- [Posts](/acquire/content/posts): the state machine the `409` responses enforce.
