# API and CLI

Give your tools access to the same posts you manage in Studio.

URL: /docs/automation

The REST API and CLI work with the same projects, posts and destinations as Studio.
An agent that can run commands can prepare a draft for you; a script can use the
same commands without reconstructing HTTP requests.

A dedicated MCP server and packaged agent skills are planned, not available yet.
The CLI walkthrough below works from a Postdot source checkout with Bun installed.
It does not require a separate CLI service or a model subscription from Postdot.

## Create a draft for review [#create-a-draft-for-review]

You need a running instance, permission to edit a project and a connected Bluesky
channel for the example below. If needed, [connect Bluesky](/docs/providers/bluesky/)
through Studio first.

### Give the integration limited access [#give-the-integration-limited-access]

In Studio, open **Settings**, then **API keys**. Create a key limited to your chosen project
and these actions:

- `project:read` to discover channels and inspect publications.
- `publication:manage` to create and edit drafts.

Leave `publication:dispatch` out. With this limited key, the integration cannot
schedule or publish. Key limits also depend on your current project role: a key
cannot grant access that its owner does not have.

Copy the secret when it is shown and store it in your agent's secret configuration
or your shell environment. You can edit the key's limits or revoke it from Studio.

<Callout title="Keep the key out of prompts and source files" type="warn">
    Do not paste the secret into a shared chat, commit it, or put it inside a content file.
</Callout>

Set `POSTBOT_URL` to your installation's origin, for example
`http://127.0.0.1:3000` for the default local Docker installation, and
`POSTBOT_API_KEY` to the key secret. Run the commands below at the source checkout root.
### Find your project and channel [#find-your-project-and-channel]

```sh
bun run postdot projects list --json
```

Copy the selected project's ID into `PROJECT_ID`, then inspect its channels:

```sh
export PROJECT_ID="REPLACE_WITH_PROJECT_ID"
bun run postdot channels list --project "$PROJECT_ID" --json
```

Use the ID of a channel you already connected in Studio:

```sh
export CHANNEL_ID="REPLACE_WITH_CHANNEL_ID"
bun run postdot channels profile --project "$PROJECT_ID" --channel "$CHANNEL_ID" --json
bun run postdot publishers list --project "$PROJECT_ID" --json
```

`channels profile` describes the destination's accepted content under
`active.profile`. `publishers list` reports recorded connection state without
returning social account tokens. Neither command connects an account or publishes.
Provider authorization takes place through a human session in Studio.
### Create a text-only Bluesky draft [#create-a-text-only-bluesky-draft]

For a built-in Bluesky channel, save this as `post.json`, replacing
`REPLACE_WITH_CHANNEL_ID` with the actual channel ID. The JSON uses the field
`text` required by that provider's profile; it is not a universal network payload.

```json
{
    "title": "Friday collection announcement",
    "sourceContent": {
        "body": {
            "text": "Our new collection arrives on Friday. Take a look and tell us your favorite.",
            "format": "plain_text"
        },
        "media": []
    },
    "publications": [
        {
            "channelId": "REPLACE_WITH_CHANNEL_ID",
            "deliveryMode": "automatic",
            "targetContent": {
                "text": "Our new collection arrives on Friday. Take a look and tell us your favorite.",
                "media": []
            }
        }
    ]
}
```

```sh
bun run postdot posts create --project "$PROJECT_ID" --input post.json --json
```
### Inspect the draft and review it in Studio [#inspect-the-draft-and-review-it-in-studio]

The response includes `post.id` and the created `publications`. Copy the destination's
publication ID to inspect it:

```sh
export PUBLICATION_ID="REPLACE_WITH_PUBLICATION_ID"
bun run postdot publications show --project "$PROJECT_ID" --publication "$PUBLICATION_ID" --json
```

There is deliberately no `scheduledAt` in the input. `automatic` selects the
channel's eventual delivery method; it does not authorize a send by itself.
The publication remains an unscheduled draft. Return to **Queue** in Studio to
review it, following [your first publication](/docs/first-publication/#4-review-in-studio).

For a manual message channel, use `deliveryMode: "manual"` and a destination payload
with `body` instead of `text`. For other networks, inspect the active profile rather
than copying Bluesky's field names. Media also has provider-specific requirements.

## Inspect before repeating a write [#inspect-before-repeating-a-write]

With `--json`, successful commands write API JSON to standard output. Failures write
an error object to standard error and return a nonzero exit code. Requests have a
30-second deadline by default; `--timeout-ms` changes that deadline.

The CLI does not automatically retry writes. A transport error can mean that the
server saved the draft but the response was lost. Inspect the current publications
before submitting the same content again:

```sh
bun run postdot publications list --project "$PROJECT_ID" --json
```

Lists are paginated. When `nextCursor` is present, pass its value with `--cursor`
to read the next page.

## Go further [#go-further]

```sh
bun run postdot --help
```

The CLI also supports media upload/import, draft updates and explicit publication
lifecycle commands. Those operations need the corresponding key actions. Keep a
draft-only key for preparation rather than expanding its access just to make an
unrelated command succeed.

The typed client is in `packages/client`; the REST API uses the same server-side
project and action checks. Built-in publisher OAuth and access administration
require a human session. Other clients do not bypass these boundaries.

