API & Webhooks
Publish to your channels from your own systems, and hear when a post goes out or a channel stops working.
API keys
API access comes with the Growth and Business plans. Make a key in Studio → Connections → API keys (the owner or an admin). Give it a name and choose what it may do:
- Integrator API: the
/public/v1publishing API below. - Content pipeline (MCP): lets scheduled AI runs read and write a brand’s plan. Most integrations don’t need it.
The key is shown once: copy it then. A key can be revoked at any time, and can be given an expiry. Keep it on your server; never put it in a web page or an app.
Send it with every request, as either header:
X-Connect-Api-Key: dck_…
Authorization: Bearer dck_…A missing or wrong key gets 401. A key without the right permission gets 403, and so does every
request from a workspace whose plan does not include the API (Starter or the free trial): “API access
comes with Growth and Business…”. A key made on Growth stops working if the workspace moves to Starter,
and works again once it is back on Growth or Business.
The publishing API
Base URL: https://studio-api.duabaconnect.com/public/v1. Every call acts on the workspace the key
belongs to. Bodies are JSON (up to 8 MB), except uploads.
The request and response shapes match Postiz’s public API, so a tool or n8n flow built for Postiz works by changing its base URL and key.
| Method and path | What it does |
|---|---|
GET /integrations | Your channels: id, name, identifier (the platform), picture, disabled, profile, customer. Add ?group= to filter by client |
GET /integrations/:id/targets | Where inside a channel a post can go: Pinterest boards, subreddits, Discord and Slack channels |
POST /upload | Upload a file (multipart field file, up to 200 MB). Returns the path to use in a post |
POST /upload-from-url | Upload from a URL: { "url": "…" } |
POST /posts | Create a post (below) |
GET /posts | Your posts and where each went; from, to, limit (up to 500) |
DELETE /posts/:id | Delete a post. A published post is also deleted on the platform where the platform allows it; otherwise the answer says it is still live |
GET /find-slot/:id | The next free posting time for a channel, within 60 days |
GET /groups | Your clients |
POST /customers | Find or create a client by name |
GET /analytics/:integration?date=30 | A channel’s figures over the last N days |
GET /analytics/post/:postId | One post’s figures, channel by channel |
GET /is-connected | Checks the key: { "connected": true } |
A path that does not exist answers 404 with the list of routes. The API does not make images: make
them in Studio’s Composer, or upload your own with POST /upload.
Creating a post
{
"type": "schedule",
"date": "2026-11-03T09:00:00Z",
"posts": [
{
"integration": { "id": "<channel id>" },
"value": [
{ "content": "Our new menu is out!", "image": [{ "path": "<path from /upload>" }] }
],
"settings": {}
}
]
}typeisnow,scheduleordraft.- One entry in
postsper channel; each can have its own words. - One
valueentry per post: threads are not supported, and neither is updating a post (delete it and create it again). - The same checks as Studio apply before anything is saved: caption length, each platform’s media rules, and Approval first on the channel.
Webhooks
Studio → Connections → Webhooks: we POST to your URL when something happens.
| Event | When |
|---|---|
post.published | A post went out |
post.failed | A post failed on a channel |
channel.disabled | A channel stopped working and needs reconnecting |
Choose the events and the channels (none chosen means all). The body is:
{ "id": "<event id>", "event": "post.published", "createdAt": "…", "organizationId": "…", "data": { } }Checking the signature
Each delivery carries X-Studio-Event, X-Studio-Event-Id and
X-Studio-Signature: t=<unix time>,v1=<signature>. The signature is an HMAC-SHA256, with your webhook’s
signing key (whsec_…, shown once when you create it), of the event id, the time and the raw body joined
by dots. Check it before trusting the body, and reject old timestamps:
import crypto from 'node:crypto';
function verify(rawBody, headers, secret) {
const id = headers['x-studio-event-id'];
const parts = Object.fromEntries(headers['x-studio-signature'].split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${id}.${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && expected.length === parts.v1?.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}A delivery that fails is tried up to five times. After 20 failures in a row the webhook is switched off, with the reason shown in Studio; turn it back on once your endpoint is fixed. Recent attempts shows each delivery and how it went.
Your own applications
Studio → Connections → Applications registers an OAuth application, so other people’s workspaces can grant it access with the usual sign-in and consent screen (authorization code with PKCE). Each workspace sees the applications it has approved there too, and can revoke them.