First 200 users get the Growth plan for $19/mo.

Claim
Browse the docs

API reference

Workspaces

Create client workspaces, mint keys for them, and issue hosted connect links, for agencies and B2B.

Base URL https://api.overads.io/public/v1. Every route needs a key with the scope shown, sent as Authorization: Bearer sk_live_YOUR_KEY. The machine-readable contract is at https://api.overads.io/public/v1/openapi.json, no key needed. Scope for this family: workspaces:manage.

These routes always act on the key's own workspace (the agency). They are the one place the x-overads-workspace header is refused: sending it answers 400 with WORKSPACE_HEADER_NOT_ALLOWED. Every other public route and every MCP tool honours the header; see Managed workspaces.

GET/workspaces

Scope workspaces:manage. The workspaces this key's workspace manages.

Response: { data: Workspace[] } with id, name, slug, managedBy (the agency workspace id) and createdAt.

curlbash
curl https://api.overads.io/public/v1/workspaces -H "Authorization: Bearer sk_live_YOUR_KEY"

POST/workspaces

Scope workspaces:manage. Create a client workspace managed by this key's workspace.

A managed workspace is a real workspace: same composer, same connections, same limits. It is owned by the agency workspace's owner, inherits the agency's plan, appears in the app switcher, and is listed under Settings > Clients.

BodyTypeMeaning
namestringrequired1 to 80 characters.
slugstringoptionalURL-safe handle, 2 to 40 lower-case letters, digits and hyphens. Derived from the name when omitted. A taken slug is refused with VALIDATION_FAILED (409).

Response: 201 with { data: Workspace, warnings: [] }.

200json
{
  "data": { "id": "c0ff…", "name": "Acme Coffee", "slug": "acme-coffee", "managedBy": "a9e1…", "createdAt": "2026-09-11T10:02:00.000Z" },
  "warnings": []
}
curlbash
curl -X POST https://api.overads.io/public/v1/workspaces -H "Authorization: Bearer sk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "name": "Acme Coffee" }'

GET/workspaces/:id

Scope workspaces:manage. One managed workspace. WORKSPACE_NOT_FOUND for any workspace this key's workspace does not manage.

Response: { data: Workspace }.

curlbash
curl https://api.overads.io/public/v1/workspaces/c0ff3e00-1a2b-4c3d-8e4f-5a6b7c8d9e0f -H "Authorization: Bearer sk_live_YOUR_KEY"

DELETE/workspaces/:id

Scope workspaces:manage. Detach a managed workspace. Nothing is deleted.

The workspace, its posts and its connections stay with their owner, now unmanaged. Every hosted connect link for it is revoked. Keys minted for it keep working for that workspace.

Response: 204, no body.

curlbash
curl -X DELETE https://api.overads.io/public/v1/workspaces/c0ff3e00-1a2b-4c3d-8e4f-5a6b7c8d9e0f -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

POST/workspaces/:id/api-keys

Scope workspaces:manage. Mint an API key that belongs to the managed workspace. The secret is returned once, here.

The key acts on the client workspace directly, with no header. It can never hold workspaces:manage (a client cannot mint clients): the scope is stripped, and a request whose only scope is that one is refused with VALIDATION_FAILED.

BodyTypeMeaning
namestringrequired1 to 80 characters.
scopesstring[]optionalAny scopes except workspaces:manage. Omitted means the calling key's scopes minus workspaces:manage.
expiresAtISO 8601 or nulloptionalWhen the key stops working. Must be in the future.

Response: 201 with { data: { id, name, prefix, scopes, expiresAt, createdAt, lastUsedAt, secret }, warnings: [] }. Store secret; it is not shown again.

curlbash
curl -X POST https://api.overads.io/public/v1/workspaces/c0ff3e00-1a2b-4c3d-8e4f-5a6b7c8d9e0f/api-keys -H "Authorization: Bearer sk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "name": "Acme integration", "scopes": ["connections:read", "posts:read", "posts:write", "media:write"] }'

POST/workspaces/:id/connect-url

Scope workspaces:manage. A time-limited hosted page where the client connects channels without an overads login. The link is returned once.

The page shows the brand name and logo you pass (or the agency workspace's name), the channel buttons, and the accounts already connected. Nothing else, and no login. See Managed workspaces.

BodyTypeMeaning
expiresInDaysintegeroptional1 to 90. Default 7.
rotatebooleanoptionalRevoke every earlier link for this workspace before minting this one.
brandobjectoptional{ name?, logoUrl? }. name up to 80 characters, defaults to the agency workspace name; logoUrl an https image URL shown above the channel tiles.

Response: 201 with { data: { url, expiresAt, rotated }, warnings: [] }. url is https://overads.io/connect/ovc_....

200json
{
  "data": { "url": "https://overads.io/connect/ovc_Qm9…", "expiresAt": "2026-09-18T10:05:00.000Z", "rotated": false },
  "warnings": []
}
curlbash
curl -X POST https://api.overads.io/public/v1/workspaces/c0ff3e00-1a2b-4c3d-8e4f-5a6b7c8d9e0f/connect-url -H "Authorization: Bearer sk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{ "expiresInDays": 14, "brand": { "name": "Northwind Social", "logoUrl": "https://cdn.northwind.example/logo.png" } }'

The hosted connect page

GET /public/connect/:token (no key) returns what the page at /connect/:token renders: { data: { workspace: { id, name }, brand: { name, logoUrl }, expiresAt, channels: { platform, configured }[], connections: { id, platform, accountName, displayName, avatarUrl, status }[], telegramBotUsername } }. POST /public/connect/:token/session (no key) mints a 15-minute session for one channel-connect attempt: { data: { session, expiresAt } }. A link that never existed or was rotated away answers 404 with CONNECT_TOKEN_INVALID; an expired one answers 410 with CONNECT_TOKEN_EXPIRED; a bad session answers 401 with CONNECT_SESSION_INVALID. These two routes exist for the hosted page; an integration only needs the connect-url route above.