Postauto

MCP server and HTTP API

Your whole account, every channel, from the tool you already work in.

One server stands in front of every channel the account has connected, with 31 typed tools behind it. Your client reads the channels, reads what each network will actually accept, writes the post and sends it.

Self serve One key opens the MCP server and the HTTP API. You make it yourself, in Settings, and nothing goes out until you or your agent says so.

Connect

Pick the client you already have

Every one of these talks to the same endpoint on the same key. Choose one for its own three steps, or take the terminal instead.

Any language, any runtime. One bearer key.

Speaks MCP over HTTP

01

Add it as a custom connector

Settings, Connectors, then add a custom connector and paste this as the server URL. It carries the key in the path, which is the form for a client that cannot attach a header, and it authenticates exactly as the header does. Revoke that key if the address ends up somewhere shared.

Connector URL
https://api.postauto.io/api/mcp/pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

02

Leave the confirmation step on

Two tools reach real timelines and nothing can take a post back: publish_post, which sends a post that is already written, and retry_target, which flies one channel again. Both are annotated so a client can see it and ask you first. Everything else reads, or writes a draft that waits for you.

Every tool, and what each one can reach

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

MCP over HTTP, or from a terminal

01

Sign in

Run /mcp inside Claude Code and follow the browser. It stores the token and refreshes it when it expires, so this is the last time you think about it. From a shell instead, claude mcp login postauto does the same.

See what you are approving

02

Or use a key, for a script with no browser

A cron job cannot follow a browser, so it wants a key instead. Make one in Settings, API keys and pass it as a header. It can publish to every channel on the account and it does not expire, so give an agent its own and revoke it when you are done.

Command
claude mcp add --transport http postauto \
  https://api.postauto.io/api/mcp \
  --header "Authorization: Bearer pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

03

Add the server

One command, no key. Claude Code registers itself with the server and works out where to sign in on its own.

Command
claude mcp add --transport http postauto https://api.postauto.io/api/mcp

Speaks MCP over HTTP

01

Add it under Connectors

Settings, Connectors, Advanced, Developer mode, then Create. Name it Postauto, paste this as the MCP server URL, and set authentication to access token or API key with the key pasted in. That sends it as a bearer header, which is the form to prefer.

Connector URL
https://api.postauto.io/api/mcp

02

If the form has no field for a token

Leave authentication set to none and use this address instead, which carries the key in the path. Either way, Postauto writes to your social channels, so leave a confirmation step on for it rather than approving every tool call in advance.

Connector URL
https://api.postauto.io/api/mcp/pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

Speaks MCP over HTTP

01

Write it into mcp.json

~/.cursor/mcp.json gives every project the server, .cursor/mcp.json in a repository gives one project it. The key travels as a header, so it stays out of the URL, which means out of proxy logs and shell history.

Configuration
{
  "mcpServers": {
    "postauto": {
      "url": "https://api.postauto.io/api/mcp",
      "headers": {
        "Authorization": "Bearer pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

02

Keep the project file out of git

A committed .cursor/mcp.json is a published key, and a key can publish to every channel on the account. Ignore the file, or put the server in the user scoped one instead. If a key does get out, revoke it in Settings and make another; that is the whole recovery.

The server, in full

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

MCP over HTTP, or from a terminal

01

Add the server to the CLI's settings

~/.gemini/settings.json for every project, .gemini/settings.json for one. httpUrl is the streamable HTTP form, which is what this endpoint speaks, and headers is where the key goes.

Configuration
{
  "mcpServers": {
    "postauto": {
      "httpUrl": "https://api.postauto.io/api/mcp",
      "headers": {
        "Authorization": "Bearer pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

02

The browser app is a different thing

Adding a remote MCP server is a CLI feature. From the app there is nothing to point at the endpoint, so the two doors from there are the CLI above and the plain HTTP API on the same key.

The HTTP API, on the same key

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

Speaks MCP over HTTP

01

Add a workspace server

.vscode/mcp.json, with an input rather than the key itself. VS Code prompts for the key the first time and keeps it, so this is the one configuration on this page that is safe to commit.

Configuration
{
  "inputs": [
    {
      "type": "promptString",
      "id": "postauto-key",
      "description": "Postauto API key (pau_live_...)",
      "password": true
    }
  ],
  "servers": {
    "postauto": {
      "type": "http",
      "url": "https://api.postauto.io/api/mcp",
      "headers": {
        "Authorization": "Bearer ${input:postauto-key}"
      }
    }
  }
}

02

Start it, then check it

The server appears in the MCP view and its tools appear in the chat's tool picker. A missing or revoked key answers 401 with a JSON-RPC error explaining how to send one, so a failure here says what is wrong rather than going quiet.

What answers what, including 405 and 406

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

Launches a server on your machine

01

Point a bridge at the endpoint

Zed starts a context server as a program on your machine, so a remote server over HTTP is reached through a stdio bridge rather than directly. This is the command to give it. The address carries the key because the bridge is what calls us.

Command
npx -y mcp-remote https://api.postauto.io/api/mcp/pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

02

A key in a settings file is still a key

It sits there in plain text, which is fine on your own machine and not fine in a dotfiles repository anybody else can read. Give this one its own key so revoking it costs you nothing else.

The server, in full

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

Speaks MCP over HTTP

01

Add it as a custom server

Windsurf keeps its MCP servers in its own configuration, reachable from the plugin panel. Paste this as the server URL, and add Authorization with Bearer and your key as a header on the same entry.

Connector URL
https://api.postauto.io/api/mcp

02

If the build will not send a header

Use this address instead. It carries the key in the path, it authenticates identically, and it is the fallback for any client that cannot attach one. Treat the URL itself as the secret from then on.

Connector URL
https://api.postauto.io/api/mcp/pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

MCP over HTTP, or from a terminal

01

Add an HTTP server to config.toml

Codex reads its servers from ~/.codex/config.toml. Add Postauto there as an HTTP server on this URL, with an Authorization header carrying Bearer and your key.

Connector URL
https://api.postauto.io/api/mcp

02

If your build only launches servers as commands

Streamable HTTP arrived in Codex after stdio did, so an older build takes a command rather than a URL. This bridges one to the other, and the address carries the key because the bridge is the caller.

Command
npx -y mcp-remote https://api.postauto.io/api/mcp/pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

Speaks MCP over HTTP

01

Add it from Manage MCP Servers

Raycast's own Manage MCP Servers command is where a server goes. Paste this endpoint, and put the key in an Authorization header as Bearer and the key if the form takes one.

Connector URL
https://api.postauto.io/api/mcp

02

Otherwise bridge it

Where the form takes a command rather than an address, this is the command. It runs on your machine and speaks HTTP to us, and the key rides in the address it is given.

Command
npx -y mcp-remote https://api.postauto.io/api/mcp/pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

Plain HTTP, on the same key

01

Check the key by hand

This is also the shape of every MCP call, and both headers are required by the transport. It answers with the whole tool list. A missing or revoked key answers 401 with a JSON-RPC error explaining how to send one.

Command
curl -sS -X POST https://api.postauto.io/api/mcp \
  -H "Authorization: Bearer $POSTAUTO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

02

Then write a post

The REST API is the same service layer behind a different door, on the same key. It lists channels and networks, splits a thread, finds the next open gate, creates, reads, revises and deletes posts, reads their numbers, and manages feeds and repost rules.

The HTTP half, in full

03

Make an API key

Sign in, then Settings, API keys. A key is pau_live_ followed by 32 characters and it is shown exactly once, because only its hash is stored. The same key opens the MCP server and the HTTP API. It can publish to every channel on the account, so treat it like a password and give an agent its own.

Open Settings, API keys

Nothing on that list is the one you use? Anything that speaks MCP over HTTP works, and anything that does not can still call the plain HTTP API on the same key.

The short version

What is MCP, and what is actually happening?

A protocol for handing a model a set of typed tools

A client you already use, a chat app or an editor or a terminal agent, asks a server what it can do. The server answers with a list of tools, each one with a name, a description and a typed set of arguments. From then on the model can call them, and the client shows you what it called and what came back.

Postauto is one of those servers. It registers 31 tools in front of 21 networks, so the model never learns 21 network APIs, never holds 21 sets of tokens, and never guesses at 21 sets of media rules.

One POST, one answer, nothing to keep open

Transport is streamable HTTP in stateless mode. There is no session to open and none to close, which is why the whole setup is a URL and a header rather than a process to supervise. GET and DELETE answer 405, because there is nothing for them to do.

Authentication is a bearer token on every call. The same token authenticates the HTTP API, so a caller with no MCP in it is not a second integration, it is the same one through a different door.

MCP server and HTTP API

Publish to 21 networks from your own agent.

One MCP server and one HTTP API stand in front of every channel the account has connected. Your agent reads the channels, reads what each network will actually accept, writes the post and sends it. It never learns 21 network APIs, holds 21 sets of tokens, or handles 21 sets of media rules.

Terminal Claude Code
claude mcp add --transport http postauto \
  https://api.postauto.io/api/mcp \
  --header "Authorization: Bearer pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

One command, and the tools below are in the session. The same key opens the HTTP API.

What that looks like in a session

halyard-site claude
  1. Typed post the Guji restock to X, Bluesky and Threads
  2. Called list_accounts ()
  3. Returned 3 channels, all active: x, bluesky, threads
  4. Called get_network_rules (network: "x")
  5. Returned maxLength 280, counted weighted, threads true
  6. Called create_post (content, targets, idempotencyKey)
  7. Returned draft b21e6c78, 3 targets pending
  8. Answered Drafted for all three. 64 characters on X, so it fits in one post. Send it?
  9. Typed send it
  10. Called publish_post (postId, idempotencyKey)
  11. Returned published, 3 of 3 departed

The post, where it landed

  1. X @halyardcoffee Departed x.com/halyardcoffee/status/1897402244
  2. Bluesky @halyardcoffee.bsky.social Departed bsky.app/profile/halyardcoffee.bsky.social/post/3l4c
  3. Threads @halyardcoffee Departed threads.net/@halyardcoffee/post/C9xNqTr

Example session. Halyard Coffee is invented for this page, and the ids and addresses in it are illustrative. Every tool name, argument name and field name is the server's own.

Step zero

One key opens both doors

Keys are self serve. There is no waitlist, no form and nobody to email. The same key authenticates the MCP server and the HTTP API, and it is the only thing either of them needs.

  1. One

    Open Settings, API keys

    Sign in and go to Settings, API keys. An account may hold up to 20 active keys, so an agent can have its own and you can revoke it without breaking anything else.

  2. Two

    Copy it once

    A key is pau_live_ followed by 32 characters, and it is shown exactly once. Postauto stores only a SHA-256 hash of it, so it cannot be shown again. Lose one and you revoke it and make another.

  3. Three

    Send it as a bearer token

    Authorization: Bearer pau_live_... on every call. Treat it like a password: it can publish to every channel on the account. It cannot mint another key, and it cannot reach anybody else's account.

Two ways to send it

The header is the one to use. It keeps the key out of URLs, which means out of proxy logs, browser history and shell history.

Some MCP clients still cannot attach a custom header. For those, and only for those, the key goes in the path instead. It authenticates identically. If that address ends up somewhere shared, revoke the key.

Path style fallback
https://api.postauto.io/api/mcp/pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The address

One endpoint, and the file it goes in

The endpoint is https://api.postauto.io/api/mcp. Transport is streamable HTTP in stateless mode: one POST, one answer, no session to open and none to close. Anything that speaks MCP over HTTP works, and GET and DELETE answer 405 because there is nothing for them to do.

.mcp.json project scoped
{
  "mcpServers": {
    "postauto": {
      "type": "http",
      "url": "https://api.postauto.io/api/mcp",
      "headers": {
        "Authorization": "Bearer pau_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

Committing it to the repository

A file like this next to your code gives everyone on the project the same tools in their session, which is usually what you want.

A file with a key in it does not belong in version control. Add it to .gitignore, or keep the server in your own user scoped configuration and leave the repository clean.

Checking a key by hand

This is also the shape of every call the server takes. Both headers are required by the transport: without the Accept line a compliant client would not be compliant, and the server answers 406.

It answers with the 31 tools below. A missing or revoked key answers 401 with a JSON-RPC error explaining how to send one.

Terminal tools/list
curl -sS -X POST https://api.postauto.io/api/mcp \
  -H "Authorization: Bearer $POSTAUTO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

The surface

31 tools, and no catch-all

Every tool is explicit and typed. There is no wrapper that will call any endpoint you name, on purpose: it hands a model a surface nobody has read, and the client directories reject it anyway. The natural order for an agent is account_status for the ceilings, then list_accounts, then get_network_rules for each target, then create_post.

Read the account

What this key can reach, filed the way the customer files it, and how much of the plan is left. An agent that reads its ceilings first does not discover them on the tenth post.

list_accounts Reads
Every connected channel with its id, network, handle, posting times and always-on sign-off. Those ids are what create_post takes as targets.
list_clients Reads
The clients an agency files its channels under, and how many channels sit under each.
resolve_client Reads
Turn a name a person said into channel ids, so "post this to Acme" is a call rather than a guess. An ambiguous name is refused, not chosen between.
account_status Reads
The plan, its channel and monthly post limits, what has been used, and what is left.
list_tags Reads
The tag vocabulary this account files posts and media under.
list_media Reads
Search the media library, so a picture can be reused instead of fetched again.

Write a post

Read the network's real rules, cut the text to fit them, then create the post. With no departure time it is saved as a draft for a person to approve, which is usually the right default for an agent.

get_network_rules Reads
Per-network rules: the length ceiling and the unit it is counted in, the shapes a post can take, whether an @mention reaches anybody, and the real per-format file rules.
split_into_thread Reads
Cut one long piece of writing into thread parts that fit a network, measured the way that network measures.
next_slot Reads
The next open gate across a set of channels.
upload_from_url Writes
Fetch an image or video from a public address and return a media id. Private, loopback and link-local addresses are refused, and every redirect hop is checked again.
create_post Publishes
Create a post for one or more channels, with at most one of scheduledAt, nextSlot or publishNow. Marked as agent-created, so the account can always see which posts an agent wrote.
update_post Writes
Revise a post that has not gone out yet, keeping its id, its share link and its tags. Refused once anything has departed.

Send it, and repair it

The half that reaches an audience. Both of the first two take an idempotencyKey, and this is the pair worth using it on: a retry after a timeout is what a well written client does with a network error, and without a key it is also how a post publishes twice.

publish_post Publishes
Send a post that is already written, now, without waiting for its schedule. Takes a postId, skips the channels that already published, and takes an idempotencyKey so a retry after a timeout cannot deliver twice.
retry_target Publishes
Fly one channel of a post again after it failed, leaving every other channel untouched. Its targetId comes from get_post, and is not the account id.
delete_post Writes
Delete a post and its channels. Refused while the post is departing.

Find out what happened

The questions an agent asks afterwards: did it land, which channel refused it and in whose words, what performed, and what needs a person.

list_posts Reads
Recent posts with per-channel statuses and error text, newest first, paged.
search_posts Reads
Find a post by its words, status group, network or date range, so "the post about the launch" has an answer that is not a year of paging.
get_post Reads
One post by id, with its channels, their ids, their statuses and their published addresses.
get_post_metrics Reads
The latest numbers for each channel one post went to, and when they were read.
top_posts Reads
The best performing posts over a window, ranked by engagement rather than by views.
list_notifications Reads
What needs attention and is not attached to a post you already know: expired channels, stopped feeds, suspended webhooks, frozen metrics, billing.
acknowledge_notifications Writes
Mark notifications read, for the agent and for the person whose account this is.

Post without being asked

Two standing instructions: turn a feed into posts, and send a post that did well a second time. Both create posts on their own schedule, which is why both are set up deliberately and neither can be triggered on demand.

list_feeds Reads
The RSS or Atom feeds this account watches, with what each has produced.
preview_feed Reads
Read a feed without saving it: its title and three items rendered exactly as they would be posted. Call this first, because a blog address that is not a feed address saves quietly and then never posts.
watch_feed Writes
Turn new items in a feed into posts on the channels you choose. Everything already in the feed is recorded as seen, so a blog with years of archive contributes nothing until it publishes again.
update_feed Writes
Change a feed's channels, its post text, its mode, or whether it is watched at all.
unwatch_feed Writes
Stop watching a feed.
list_repost_rules Reads
The rules that send a past post again, with what each has sent.
create_repost_rule Writes
Send a post that performed well a second time, inside limits you set: cooldownDays, maxReposts and lookbackDays. A rule never reposts its own output.
update_repost_rule Writes
Change a rule's limits, its channels, or whether it runs.
delete_repost_rule Writes
Remove a rule. The posts it already sent are left alone.

The call the session made

create_post takes content and targets, plus optional media, overrides, parts, tags and per-network settings, and at most one of scheduledAt, nextSlot or publishNow. With none of those three the post is saved as a draft for a person to approve, which is usually the right default for an agent running on its own.

idempotencyKey is what makes a retry after a timeout safe. Send the same key again and you get the first call's answer back rather than a second post. Keys are honoured for 24 hours.

create_post arguments
{
  "content":
    "Six bags of the Ethiopia Guji left, then it is gone until March.",
  "targets": [
    "0a9c2f1e-6b7c-4a1e-9d3f-2b5c8e7a1d40",
    "7d1b4c30-11aa-4f52-8a90-3c6d5e2f1b88",
    "c4f8a5b2-90d7-4e63-b1a8-5f2c6d3e9a11"
  ],
  "idempotencyKey": "guji-restock-2026-09-01"
}

Before you wire it into a loop

What it will not do

The refusals are the interesting part of an API an agent drives unattended. Each of these is something the server actually refuses, and the message it returns says which channel, which limit or which id was the problem.

There is no tool that calls any endpoint

Every tool is explicit and typed, on purpose. A catch-all wrapper is rejected by both major client directories, and it hands a model a surface nobody has read.

Publishing cannot be undone

Not by this API, not by the app, and on several networks not by the network. publish_post and retry_target are annotated so a client can see it and ask a person first. On a post nobody has read, ask.

A post that has left cannot be edited

draft, scheduled and failed can be revised. publishing, published and partial are refused with the reason, and the check happens inside the write, so a scheduled post that departs while your call is in flight is refused rather than edited on its way out.

Do not count the characters yourself

Every network counts differently. X weighs a whole emoji family as 2, Bluesky counts it as 1, and Threads counts the string in UTF-8 bytes, where one curly apostrophe is worth 3. get_network_rules returns the unit, and split_into_thread does the arithmetic.

Some failures are refused rather than retried

If a publish was interrupted after it started and before the network answered, nobody knows whether it went out. That channel is refused, by name, and takes confirmMayAlreadyBeLive on the next call once you have opened the channel and looked.

Nothing runs a feed or a rule on demand

There is deliberately no tool for it. Both create posts, and an agent with a loop is the caller most likely to call one in one. The schedule reaches the same result within the half hour, and create_post is there when a post is wanted now.

The other front door

Plain HTTP, for a caller with no MCP in it

The REST API is the same service layer under a different door, on the same key, at https://api.postauto.io/api/v1. It lists channels and networks, splits a thread, finds the next gate, creates, reads, revises and deletes posts, reads their numbers, and manages feeds and repost rules.

Terminal POST /api/v1/posts
curl -X POST https://api.postauto.io/api/v1/posts \
  -H "Authorization: Bearer $POSTAUTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Six bags of the Ethiopia Guji left.",
    "targets": ["0a9c2f1e-6b7c-4a1e-9d3f-2b5c8e7a1d40"],
    "nextSlot": true
  }'

Where the two doors differ

Two things are MCP only today, and both are the finishing half of publishing: publish_post, which sends a post that is already written, and retry_target, which flies one channel again after it failed. Over HTTP a post goes out by being scheduled, or by passing publishNow when you create it.

Reading a customer's own client names, searching posts by their words and reading notifications are MCP only as well. Everything else is on both.

How much you may ask for

120 a minute

Reads, per key

Every GET on /api/v1.

30 a minute

Writes, per key

POST, PATCH, PUT and DELETE on /api/v1, and every MCP call including the tool list, because every MCP call is a POST.

20 a minute

Per address, before the key

Charged on every request that reaches the API, so a bad key cannot be guessed at in a loop.

When something answers with a number

401
No header and no path key, or the key was revoked. Make a new one in Settings, API keys.
405
The client sent GET or DELETE. The endpoint is stateless and answers POST only, so there is no stream to open and no session to end.
406
The client did not send Accept: application/json, text/event-stream. Every compliant MCP client does.
429
A budget above is spent. Wait the seconds named in Retry-After.

Anything else that comes back as a message rather than as data is the answer: it names the network that refused the post, the limit that was passed, or the id that does not exist.

Make a key

Make a key and try it

Connect one channel, add the server to your client, and ask it to draft something. The draft waits for you, and nothing goes out until you say so.