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_.
- Open your tokens page.
- Click Create token. Name it after where you'll use it (“CLI”, “Zapier”, etc.).
- Copy the token. It only shows once.
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/jsonon 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
- Connect at least one social account at /dashboard/accounts.
- Make a request:
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.
curl https://tinyposter.app/api/v1/health \
-H "Authorization: Bearer tp_..."{
"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.
{
"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.
{
"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:
{
"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:
{
"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.
# 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.
curl -X DELETE https://tinyposter.app/api/v1/posts/8a3fbb20-... \
-H "Authorization: Bearer tp_..."Errors
Every error has the same shape:
{
"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 revokedinvalid_request(400) — body or query failed validation;detailsarray tells you which fieldsnot_found(404) — post id doesn't exist or isn't yoursforbidden(403) — e.g. trying to cancel an already-published postquota_exceeded(402) — you've hit your monthly post limitplatform_not_connected(409) — you asked to post somewhere you haven't connectedrate_limited(429) — slow down. Respect theRetry-Afterheader.upstream_error(502) — the social platform's API failed. Retry later.internal_error(500) — our bug. Include therequest_idwhen contacting support.
Platforms
The platforms array uses these exact strings:
TWITTER => X (Twitter)
INSTAGRAM => Instagram
FACEBOOK => Facebook
LINKEDIN => LinkedIn
TIKTOK => TikTok
YOUTUBE => YouTube
PINTEREST => Pinterest
BLUESKY => Bluesky
THREADS => Threads
REDDIT => Reddit
MASTODON => MastodonIdempotency
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.
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
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
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
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
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.