Developers
API and MCP
Create, schedule and publish posts from your own scripts and AI agents, with the same checks as the composer. Every number on this page is read from the code that enforces it.
Start here
A key belongs to one workspace. Make one in Settings, API keys; it is shown once.
curl https://dripost.com/api/v1/workspace \
-H "Authorization: Bearer dp_your_key"- Base URL: https://dripost.com/api/v1. Send the key as Authorization: Bearer dp_…
- Scopes: read only reads; post can also create, change, publish and delete; webhooks manages webhook endpoints.
- Anything outside the key's workspace answers 404, never 403.
- Errors are JSON: { "error": "one sentence", "code": "machine_code" }.
- Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A 429 also carries Retry-After.
- POST /posts, /posts/{id}/publish and /media/* accept an Idempotency-Key header: a retry with the same key and body replays the first answer for 24 hours.
Endpoints
Every path in the OpenAPI file, generated from it. The file has the request and response shapes.
| Method | Path | What it does |
|---|---|---|
| GET | /workspace | The key's workspace, scope and plan limits |
| GET | /channels | Connected channels with live health |
| GET | /posts | List posts, newest first |
| POST | /posts | Create a post: a draft, scheduled, or published now |
| GET | /posts/{id} | One post with per-network delivery |
| PATCH | /posts/{id} | Change a post that has not gone out, or restore a deleted one |
| DELETE | /posts/{id} | Delete a draft (for good), or a post still waiting (undoable for 24h) |
| POST | /posts/{id}/publish | Schedule, queue, or publish now |
| POST | /posts/{id}/retry | Retry exactly one network whose delivery FAILED |
| GET | /jobs/{id} | Publish or media-import job status |
| POST | /variants/{id}/optimize | Opt-in AI rewrite of one variant (spends an AI credit) |
| GET | /media | The media library, newest first |
| POST | /media/presign | Step 1 of an upload: get a URL to PUT the file to |
| POST | /media/import | Import an image or video from a public HTTPS URL, byte for byte |
| POST | /media/finalize | Step 2 of an upload: the file is checked by its bytes and added to the library |
| GET | /webhooks | Webhook endpoints of this workspace |
| POST | /webhooks | Register a webhook endpoint; the signing secret is returned once |
| GET | /webhooks/{id} | One webhook endpoint |
| PATCH | /webhooks/{id} | Change the URL/events, and/or rotate the signing secret |
| DELETE | /webhooks/{id} | Remove a webhook endpoint |
| POST | /webhooks/{id}/test | Send a webhook.test event right now, outside the retry queue |
| GET | /webhooks/{id}/deliveries | The last 20 deliveries to one endpoint, newest first |
Publishing now needs your say-so
An app can draft, schedule and queue posts. Publishing this minute is off until a workspace owner turns it on, and then a person approves every one on Dripost.
- Turn it on in Settings, Connected apps, "Allow immediate publish". It cannot be turned on for the Free plan.
- POST /posts/{id}/publish with { "when": "now" } answers 202 needs_confirmation, a preview, a confirm token and an approval link. Nothing is published by the app itself.
- A person approves on Dripost: from the approval link (signed in as the person who made the key or connected the app) or from the email sent to the workspace owner. The first one used counts; the other stops working. Both expire after 30 minutes.
- Send the same call again with the confirm token to see the outcome: approved, still waiting, or declined. MCP clients that support URL elicitation show the link themselves.
- Scheduling ({ "when": "at" }) and the queue ({ "when": "queue" }) need neither step.
- Posts from an app go through the same approvals as posts from the composer.
Limits by plan
Counted per key or per connected app. Past a limit the answer is 429 and names the window.
| Free | Creator | Pro | Agency | |
|---|---|---|---|---|
| API keys per workspace | 1 | 3 | 10 | 20 |
| Requests in any one minute | 100 | 300 | 600 | 1,200 |
| Requests per rolling 24 hours | 250 | 1,500 | 5,000 | 12,000 |
| Requests per rolling 30 days | 3,000 | 20,000 | 60,000 | 150,000 |
| X posts per month through the API | 0 | 60 | 300 | 1,000 |
| Apps may publish immediately | No | If you turn it on | If you turn it on | If you turn it on |
Free also allows one workspace per account. Sign-ups from throwaway email services are refused, and creating a key needs a verified email and a browser check.
Limits by platform
Per connected channel, over a rolling 24 hours, counting posts that actually went out. They apply to every post, from the composer too, and are there to keep your accounts clear of the platforms’ own spam limits.
| Platform | Free | Creator, Pro, Agency | Note |
|---|---|---|---|
| 10 posts | 20 posts | We also read Instagram's own count and stop at 90 % of it. | |
| 10 posts | 20 posts | ||
| Threads | 25 posts, 50 replies | 50 posts, 100 replies | We also read Threads' own count and stop at 90 % of it. |
| X | 7 posts, at most 2 in 30 min | 15 posts, at most 5 in 30 min | Each part of a thread counts as one post. |
| YouTube | 1 posts | 3 posts | Google keeps videos uploaded through an app it has not audited private. Ours has not been audited yet, so every video Dripost uploads to YouTube, through the API or the composer, is private until you change it on YouTube. |
| 2 posts | 5 posts | Limited while we are on LinkedIn's development access tier, which caps how much one app may post. We will raise it when LinkedIn grants us a higher tier. |
Shared daily budgets
- X: 6,400 posts a day across all of Dripost (the other 1,600 are kept for retries). Past it, a post is refused with "X daily posting budget" and can be retried the next day.
- YouTube: 80 posts a day across all of Dripost. Past it, a post is refused with "YouTube daily upload budget" and can be retried the next day.
- LinkedIn: 450 posts a day across all of Dripost. Past it, a post is refused with "LinkedIn daily posting budget" and can be retried the next day.
Bursts and repeats
- At most 10 "publish now" calls per workspace per minute through the API. The rest are not dropped: they go to the next free slot in your queue, and the answer says "queued_by_limit".
- The same text to the same channel within 24 hours is refused (duplicate_content). We compare after trimming, lower-casing, collapsing spaces, and removing trailing hashtags and tracking parameters from links.
- The same text to two or more X accounts in one workspace within 7 days is refused.
- The same video and title to one YouTube channel within 24 hours is refused.
- The same text to different platforms is fine.
Webhooks
Register up to 10 HTTPS endpoints per workspace. Each delivery is signed; check the signature before you trust the body.
- Events: post.live, post.failed, channel.disconnected, webhook.test. Payloads carry ids, the platform, the link and the error, never the post's text.
- Dripost-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.rawBody" keyed with your whsec_ secret>. Reject a timestamp more than 5 minutes off your clock.
- While a secret is being rotated, for 24 hours, the header carries a v1 for the new secret and one for the old; accept either.
- Delivery is at least once. Use the Dripost-Delivery-Id header to ignore a repeat.
- A failed delivery is tried 8 times in all, after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h. Redirects are not followed.
- After 10 events in a row cannot be delivered, or at once on 410 Gone, the endpoint is turned off and the workspace owner is emailed.
import crypto from "node:crypto";
// rawBody: the request body exactly as it arrived, as a string, before JSON.parse.
export function verifyDripost(secret, header, rawBody, toleranceSeconds = 300) {
const parts = (header ?? "").split(",").map((p) => p.trim().split("="));
const t = Number(parts.find(([k]) => k === "t")?.[1]);
const signatures = parts.filter(([k]) => k === "v1").map(([, v]) => v);
if (!Number.isInteger(t) || signatures.length === 0) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
return signatures.some((s) => {
const given = Buffer.from(s ?? "", "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
}import hashlib, hmac, time
# raw_body: the request body exactly as it arrived, as bytes, before json.loads.
def verify_dripost(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
parts = [p.strip().partition("=") for p in (header or "").split(",")]
t = next((v for k, _, v in parts if k == "t"), "")
signatures = [v for k, _, v in parts if k == "v1"]
if not t.isdigit() or not signatures:
return False
if abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in signatures)MCP server
For AI agents: ten tools over the same service, at https://mcp.dripost.com/mcp.
Live since 27 Sept 2026
Tested end to end with Claude and ChatGPT on 27 Sept 2026: each connected and ran a real action. The other clients below have not been run against it yet. See all 10 actions →
Claude Code
- Add the server, then run /mcp inside Claude Code and choose Dripost to sign in and approve access.
- To use an API key instead of signing in, add the header shown in the second line.
claude mcp add --transport http dripost https://mcp.dripost.com/mcp claude mcp add --transport http dripost https://mcp.dripost.com/mcp --header "Authorization: Bearer dp_your_key"Cursor
- Open the install link below in a browser, or add the entry to ~/.cursor/mcp.json yourself.
- Without a headers entry, Cursor asks you to sign in to Dripost and approve access.
cursor://anysphere.cursor-deeplink/mcp/install?name=dripost&config=eyJ1cmwiOiJodHRwczovL21jcC5kcmlwb3N0LmNvbS9tY3AifQ== { "mcpServers": { "dripost": { "url": "https://mcp.dripost.com/mcp", "headers": { "Authorization": "Bearer dp_your_key" } } } }n8n
- Add an MCP Client Tool node to your AI Agent.
- Endpoint: https://mcp.dripost.com/mcp. Authentication: Bearer, with a Dripost API key.
- Server Transport: HTTP Streamable. If your version's list shows only Server-Sent Events, switch the field to Expression and enter httpStreamable.
Claude.ai (custom connector)
- Settings, Connectors, Add custom connector.
- Name it Dripost, and use https://mcp.dripost.com/mcp as the URL. Leave the OAuth fields empty.
- Connect, sign in to Dripost, choose the workspace and the permissions, and approve.
What we keep
Each API or MCP call leaves a record: which key or app, which operation, the result, the time and the country. No post text is in it. The privacy policy says what else the API stores and for how long.