REST

HTTP API

Bearer-token authenticated REST API. The same surface every other tinyposter integration is built on top of.

Get a token

Every request needs a bearer token. It starts with tp_.

  1. Open your tokens page.
  2. Click Create token. Name it after where you'll use it (“CLI”, “Zapier”, etc.).
  3. Copy the token. It only shows once.
Keep this private
Anyone with this token can post for you. If it leaks, revoke it on the same page.

The shape

  • Base URL: https://tinyposter.app/api/v1
  • Auth: Authorization: Bearer tp_… header on every request
  • Format: JSON in, JSON out. Content-Type: application/json on POSTs
  • OpenAPI 3.1 spec: /api/v1/openapi.json
  • CORS: open. Bearer auth makes it safe to call from a browser.
  • Rate limit: 240 requests/minute per token

Quickstart

  1. Connect at least one social account at /dashboard/accounts.
  2. Make a request:
bash
curl https://tinyposter.app/api/v1/posts \
  -H "Authorization: Bearer tp_paste_your_token" \
  -H "Content-Type: application/json" \
  -d '{"text":"hello world","platforms":["TWITTER"]}'

Endpoints

GET /health

Returns 200 always. Tells you whether your bearer token is valid. No quota cost. Useful to verify a token before using it.

bash
curl https://tinyposter.app/api/v1/health \
  -H "Authorization: Bearer tp_..."
json
{
  "ok": true,
  "authenticated": true,
  "user_id": "8a3fbb20-1234-5678-aaaa-bbbbccccdddd",
  "server_time": "2026-04-28T18:30:12.453Z"
}

GET /accounts

List the user's connected social platforms. Use this before posting.

json
{
  "data": [
    { "platform": "TWITTER", "username": "lizonthewebauto", "display_name": "Liz", "status": "connected" },
    { "platform": "LINKEDIN", "username": null, "display_name": "Liz Elliott", "status": "connected" }
  ]
}

GET /usage

Plan, posts used, posts remaining, when the period rolls over.

json
{
  "plan": "creator",
  "status": "active",
  "used": 12,
  "limit": 150,
  "unlimited": false,
  "period_start": "2026-04-01",
  "renews_at": "2026-05-01T00:00:00Z"
}

POST /posts

Create a post. Omit scheduled_at to publish now. Set it to an ISO datetime to schedule.

Headers:

  • Authorization: Bearer tp_… (required)
  • Content-Type: application/json (required)
  • Idempotency-Key: <random-string> (recommended for retries — repeat requests with the same key return the original response for 24h)

Body:

json
{
  "text": "Friday recap incoming",
  "platforms": ["TWITTER", "LINKEDIN"],
  "scheduled_at": null,
  "title": "optional",
  "media_urls": ["https://example.com/img.jpg"],
  "per_platform_text": {
    "TWITTER": "Short version",
    "LINKEDIN": "Long-form version with paragraph breaks."
  }
}

Response — 201 Created:

json
{
  "post": {
    "id": "8a3fbb20-1234-5678-aaaa-bbbbccccdddd",
    "status": "publishing",
    "text": "Friday recap incoming",
    "platforms": ["TWITTER", "LINKEDIN"],
    "title": null,
    "scheduled_at": "2026-04-28T18:31:12Z",
    "published_at": null,
    "error": null,
    "source": "api",
    "created_at": "2026-04-28T18:30:12Z"
  }
}

GET /posts

List your posts. Filter by date and status.

bash
# All posts (newest first, capped at 50)
curl https://tinyposter.app/api/v1/posts -H "Authorization: Bearer tp_..."

# Only scheduled, in May
curl "https://tinyposter.app/api/v1/posts?from=2026-05-01T00:00:00Z&to=2026-05-31T23:59:59Z&status=scheduled" \
  -H "Authorization: Bearer tp_..."

GET /posts/:id

Fetch a single post by id.

DELETE /posts/:id

Cancel a scheduled or queued post. Already-published posts can't be canceled.

bash
curl -X DELETE https://tinyposter.app/api/v1/posts/8a3fbb20-... \
  -H "Authorization: Bearer tp_..."

Errors

Every error has the same shape:

json
{
  "error": {
    "code": "platform_not_connected",
    "message": "Not connected: INSTAGRAM. Connect them on the Accounts page.",
    "request_id": "8a3fbb20-1234-..."
  }
}

Codes you might see:

  • unauthorized (401) — token missing, wrong, or revoked
  • invalid_request (400) — body or query failed validation; details array tells you which fields
  • not_found (404) — post id doesn't exist or isn't yours
  • forbidden (403) — e.g. trying to cancel an already-published post
  • quota_exceeded (402) — you've hit your monthly post limit
  • platform_not_connected (409) — you asked to post somewhere you haven't connected
  • rate_limited (429) — slow down. Respect the Retry-After header.
  • upstream_error (502) — the social platform's API failed. Retry later.
  • internal_error (500) — our bug. Include the request_id when contacting support.

Platforms

The platforms array uses these exact strings:

text
TWITTER  =>  X (Twitter)
INSTAGRAM  =>  Instagram
FACEBOOK  =>  Facebook
LINKEDIN  =>  LinkedIn
TIKTOK  =>  TikTok
YOUTUBE  =>  YouTube
PINTEREST  =>  Pinterest
BLUESKY  =>  Bluesky
THREADS  =>  Threads
REDDIT  =>  Reddit
MASTODON  =>  Mastodon

Idempotency

POST /posts accepts an Idempotency-Key header (8-128 chars, A-Z, a-z, 0-9, _, -). Send a unique random string per logical attempt — usually a UUID. Repeats with the same key replay the original response for 24 hours, so you can retry safely without double-posting.

When to use
Always use idempotency keys when retries are possible: cron jobs, queues, agent loops, mobile apps, anywhere a network blip could cause a re-send.

OpenAPI 3.1 spec

The full machine-readable spec lives at /api/v1/openapi.json. Import it directly into:

  • ChatGPT Custom GPT Actions
  • Postman / Insomnia / Bruno (paste the URL)
  • Stainless / Speakeasy / Fern (auto-generate client SDKs)
  • Zapier / Make.com OpenAPI connectors

Code samples

Node.js

javascript
const res = await fetch("https://tinyposter.app/api/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TINYPOSTER_TOKEN}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    text: "hello world",
    platforms: ["TWITTER", "LINKEDIN"],
  }),
});
const json = await res.json();
if (!res.ok) throw new Error(json.error.message);
console.log(json.post.id);

Python

python
import os, uuid, requests

r = requests.post(
    "https://tinyposter.app/api/v1/posts",
    headers={
        "Authorization": f"Bearer {os.environ['TINYPOSTER_TOKEN']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"text": "hello world", "platforms": ["TWITTER", "LINKEDIN"]},
    timeout=15,
)
r.raise_for_status()
print(r.json()["post"]["id"])

Go

go
body := strings.NewReader(`{"text":"hi","platforms":["TWITTER"]}`)
req, _ := http.NewRequest("POST", "https://tinyposter.app/api/v1/posts", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("TINYPOSTER_TOKEN"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()

Ruby

ruby
require "net/http"; require "json"; require "securerandom"
uri = URI("https://tinyposter.app/api/v1/posts")
req = Net::HTTP::Post.new(uri, {
  "Authorization" => "Bearer #{ENV['TINYPOSTER_TOKEN']}",
  "Content-Type" => "application/json",
  "Idempotency-Key" => SecureRandom.uuid,
})
req.body = { text: "hi", platforms: ["TWITTER"] }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body).dig("post", "id")

FAQ / Troubleshooting

How do I avoid double-posting on retry?

Send an Idempotency-Key header. Same key within 24h returns the original response.

Can I use this from a browser app?

Technically yes — CORS is open. But putting tokens in browser code means they're visible to anyone with devtools. Either keep the token on a backend you control, or use the share-link flow which uses session cookies.

What's the difference between this and the MCP server?

Same actions, different protocol. REST is for everything (browsers, curl, ChatGPT actions, the CLI). MCP is the standard AI agents speak natively (Claude Desktop, Claude Code, MCP-aware agents). Use whichever your tool wants.