Skip to content

Agent REST API

Keys, scopes, drafts, publishing, webhooks, limits and errors for the /api/agent/v1 REST API.


The Agent REST API lets scripts and agents read your brands, create drafts and schedule them, with a key you control. It sits beside the MCP connector: use the connector inside an AI chat, and the REST API from your own code.

What the REST API does not do
It creates text drafts and schedules drafts. It does not generate content: the old /generate/* and POST /repurpose endpoints answer 410 with brandos_generation_retired. For images, carousels and video, use the MCP connector or the dashboard.

Quickstart

1. Create a key

  1. Open Settings → Agent access keys and choose Create key.
  2. Give it a label, pick a preset — Read-only analyst, Content agent or Publishing agent — and adjust the scopes.
  3. Under Brands, keep All brands (current + future) or choose Specific brands only. Pick an expiry and, optionally, an IP allowlist.
  4. Copy the key. It is shown once and starts with rbos_live_.

Only owners and admins can issue a key with publish:execute, accounts:manage or automations:execute. When an operator asks for one, it waits under Pending key requests until an owner or admin approves it.

2. Check the key

curl https://ops.rebrandos.com/api/agent/v1/whoami \
  -H "Authorization: Bearer $REBRANDOS_KEY"
{
  "ok": true,
  "keyId": "brandos_agent_key_6b1d…",
  "label": "Publishing bot",
  "organizationId": "org_northwind",
  "scopes": ["brands:read", "accounts:read", "drafts:read", "drafts:write", "schedule:read", "publish:execute"],
  "brands": ["northwind-coffee"],
  "expiresAt": "2027-01-17T09:00:00.000Z",
  "env": "live"
}

brands is null when the key may use every brand.

3. Find the brand and channel

curl "https://ops.rebrandos.com/api/agent/v1/brands" \
  -H "Authorization: Bearer $REBRANDOS_KEY"
{ "ok": true, "data": [{ "id": "northwind-coffee", "name": "Northwind Coffee", "status": "active" }], "nextCursor": null, "total": 1 }
curl "https://ops.rebrandos.com/api/agent/v1/accounts" \
  -H "Authorization: Bearer $REBRANDOS_KEY"
{ "ok": true, "data": [{ "brandId": "northwind-coffee", "platform": "linkedin", "account": "Northwind Coffee" }], "nextCursor": null, "total": 1 }

Use a brand’s id as brand, and an account’s platform as channel.

4. Create a draft

curl -X POST https://ops.rebrandos.com/api/agent/v1/drafts \
  -H "Authorization: Bearer $REBRANDOS_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: draft-2026-10-19-onboarding" \
  -d '{"brand":"northwind-coffee","channel":"linkedin","title":"Onboarding week","content":"Week one at Northwind starts with a cup…"}'
201 Created
{ "ok": true, "draft": { "id": "…", "title": "Onboarding week", "brand": "northwind-coffee", "channel": "linkedin", "status": "draft" } }

5. Publish

Try it with dryRun first: you get the plan, the account’s capacity check and your remaining daily posts, and nothing is scheduled.

curl -X POST https://ops.rebrandos.com/api/agent/v1/publish \
  -H "Authorization: Bearer $REBRANDOS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"draftId":"DRAFT_ID","scheduledAt":"2026-10-20T09:00:00+01:00","dryRun":true}'

Then send the same request without dryRun, with an Idempotency-Key:

201 Created
{
  "ok": true,
  "scheduled": { "scheduleId": "…", "draftId": "…", "brand": "northwind-coffee", "channel": "linkedin", "scheduledAt": "2026-10-20T08:00:00.000Z" },
  "dailyCap": { "used": 1, "limit": 10, "remaining": 9, "resetAt": "2026-10-19T24:00:00Z" }
}

scheduledAt must be in the future and include a time zone; leave it out to schedule five minutes from now. Follow the post with GET /publish-jobs and GET /publish-receipts, or a webhook.

The REST API publishes without a chat approval
Unlike the connector, POST /publish schedules straight away within the key’s limits. To make a person approve every agent post, an admin turns on Require approval before agents publish in Settings → Agent access keys. Then POST /publish returns 202 with "status": "pending_approval", and the post waits under Pending agent actions.

Endpoints

Method and pathScopeWhat it does
GET /whoami—The key’s identity, scopes, brands and expiry.
GET /brandsbrands:readBrands the key can reach.
GET /brand-profile?brandId=brands:readThe brand’s positioning, audience, voice and pillars.
GET /content-outcomes?brandId=brands:readThe brand’s content outcomes and the outcome catalog.
GET /hook-devicesbrands:readThe hook-device catalog.
GET /pillars?brandId=pillars:readThe brand’s content pillars.
GET /viral-formatspillars:readPost formats, with a recommendation from the brand’s results when you pass brandId.
GET /accountsaccounts:readConnected accounts per brand.
GET /draftsdrafts:readDrafts, optionally for one brandId.
POST /draftsdrafts:writeCreate a text draft.
PATCH /drafts/{id}drafts:writeChange title, content, channel or postType. POST works too.
DELETE /drafts/{id}drafts:writeDelete a draft.
POST /publishpublish:executeSchedule a draft.
GET /publish-jobsschedule:readScheduled and queued posts.
POST /publish-jobs/{id}/cancelschedule:writeCancel a queued post before it starts. Works even when the workspace is read-only.
GET /publish-receiptsanalytics:readWhat each network reported after publishing.
GET /analyticsanalytics:readPerformance snapshots per brand.
GET /mediamedia:readFiles in a brand’s library.
POST /subtokens—Mint a narrower key for a sub-agent.
GET, POST /webhookswebhooks:manageList or create webhook subscriptions.
DELETE /webhooks/{id}webhooks:manageDelete a subscription.
GET /webhooks/{id}/deliverieswebhooks:manageThe last 50 delivery attempts.

List endpoints take limit (1–200, default 50) and cursor, and return data, nextCursor and total. Pass nextCursor back as cursor until it is null. Endpoints that read social publishing data also need the workspace’s social publishing module to be active.

Authentication and scopes

Send the key in the Authorization: Bearer header on every request; keys in the URL are ignored. Keys look like rbos_live_ followed by 36 letters and digits that end in a checksum, so secret scanners can spot them.

A key can never do more than the person who created it can do right now. If that person loses access to the workspace, the key stops working (issuer_no_access); if they lose write access, its write scopes stop (issuer_cannot_write).

The scopes the REST API checks are in the endpoint table. Keys can also hold the connector’s scopes (personas, calendar, schedule:write, media:write, automations), which the REST endpoints don’t use. billing:* and keys:* can never be granted to a key.

Brand restriction

A key limited to specific brands only lists those brands, their accounts, drafts, jobs, receipts and media. Naming another brand returns 403 brand_not_permitted, and its webhooks only receive events about its own brands.

Expiry, rotation and revocation

  • Every key expires. Choose 7, 30 or 90 days or 1 year when you create it. After that, requests return 401 expired.
  • Rotate from the key’s row in Settings. You get a new key straight away and the old one keeps working for 24 hours (401 rotated after that). Rotation keeps the scopes, brands, limits and expiry.
  • Revoke to stop a key immediately. Its sub-tokens stop too, and so do its webhooks.
  • ReBrandOS can revoke a leaked key automatically when a secret-scanning service reports it. For keys pushed to a code host, that needs ReBrandOS to be registered as a secret-scanning partner with that host (for example GitHub’s partner program), so don’t rely on it: if a key leaks, revoke it yourself straight away.
  • Pause all agent access stops every key, connector and webhook in the workspace (403 killed) until an admin turns it off.

IP allowlist

Add IPv4 or IPv6 addresses or CIDR ranges, separated by commas or spaces, for example 203.0.113.0/24, 2001:db8::/32. Requests from anywhere else return 403 ip_not_allowed. Leave it empty to allow any address. An entry that isn’t a valid address or range is rejected when you save, rather than being dropped.

Sub-tokens

Give a sub-agent a narrower key without creating one in Settings. A sub-token can only narrow its parent: fewer scopes, fewer brands, lower limits, an IP range inside the parent’s, an earlier expiry. Revoking, rotating or expiring the parent disables it, and a sub-token cannot mint its own.

curl -X POST https://ops.rebrandos.com/api/agent/v1/subtokens \
  -H "Authorization: Bearer $REBRANDOS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"Caption checker","scopes":["drafts:read"],"brandIds":["northwind-coffee"],"caps":{"rateLimitPerMin":60},"expiresAt":"2026-10-26T00:00:00Z"}'
201 Created
{
  "ok": true,
  "token": "rbos_live_…",
  "apiKey": { "id": "…", "label": "Caption checker", "scopes": ["drafts:read"], "brandIds": ["northwind-coffee"],
              "caps": { "rateLimitPerMin": 60, "publishesPerDay": 10, … }, "expiresAt": "2026-10-26T00:00:00.000Z",
              "env": "live", "parentKeyId": "…" }
}

Rate limits and fair use

  • 300 requests a minute per key; sub-tokens share their parent’s budget. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (Unix seconds). Over the limit you get 429 rate_limited with a Retry-After header in seconds.
  • 10 scheduled posts a day per key, shared with its sub-tokens, reset at 00:00 UTC. Successful POST /publish responses include dailyCap; over the cap you get 429 publish_cap_reached with resetAt. Dry runs and posts waiting for approval don’t count.
  • A key can have lower limits than these, never higher. If you need more, contact us; see the fair-use section of the Terms.
  • Request bodies are limited to 1 MB.

Idempotency

POST /drafts and POST /publish accept an Idempotency-Key header, scoped to your key. Retrying with the same key and the same body returns the first response instead of creating a second draft or post. The same key with a different body returns 409 idempotency_conflict; a retry while the first request is still running returns 409 idempotency_in_progress. Use a fresh key for each new draft or post, and the same key when you retry after a timeout.

Webhooks

Instead of polling, subscribe an https endpoint on a public host (private, loopback and link-local addresses are refused). The response includes the signing secret, once.

curl -X POST https://ops.rebrandos.com/api/agent/v1/webhooks \
  -H "Authorization: Bearer $REBRANDOS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/rebrandos","events":["post.published","post.failed"]}'
201 Created
{
  "ok": true,
  "webhook": { "id": "…", "url": "https://hooks.example.com/rebrandos", "events": ["post.published", "post.failed"], "status": "active" },
  "secret": "whsec_…",
  "signatureScheme": "HMAC-SHA256 over `${X-RebrandOS-Timestamp}.${rawBody}`; compare X-RebrandOS-Signature (sha256=...)"
}

Events

EventSent whendata
draft.createdA key creates a draft.draftId, brand, channel, title, source
draft.updatedA key updates a draft.draftId, brand
post.scheduledA post is scheduled through the API or connector, or a person approves a pending agent publish.draftId, brand, channel, scheduledAt, scheduleId, source
post.publishedA network confirms a post went live (any post in the workspace).brand, channel, provider, status, publishJobId, publishReceiptId, providerPostId, permalinkUrl
post.needs_actionA scheduled post was stopped before it was sent, so nothing was posted. A person must act: choose its account, review it, or pick a new time. Sent once each time a post is stopped.brand, channel, status, publishJobId, needsAction (kind, reasonCode, reason, fix, scheduledAt, pastDue)
post.failedA network reports a failed post, or a person denies a pending agent publish (then data has decision: "denied"). A stopped post is post.needs_action, not post.failed.The same fields as post.published, or draftId, brand, channel, decision
key.revokedSomeone revokes a key in Settings.apiKeyId

Omit events to receive all of them. Each event is sent once; TikTok inbox deliveries are not publications and send nothing.

Request format and signature

POST /rebrandos HTTP/1.1
Content-Type: application/json
X-RebrandOS-Event: post.published
X-RebrandOS-Timestamp: 1760950800
X-RebrandOS-Signature: sha256=5d0c…

{"id":"evt_…","type":"post.published","createdAt":"2026-10-20T08:00:03.000Z","data":{ … }}

The signature is a hex HMAC-SHA256 of the timestamp, a dot, and the raw request body, keyed with your secret. Verify it before you parse the body:

import crypto from 'node:crypto';

export function verifyRebrandosWebhook(headers, rawBody, secret) {
  const timestamp = headers['x-rebrandos-timestamp'] || '';
  const received = headers['x-rebrandos-signature'] || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`, 'utf8')
    .digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300; // our suggestion: 5 minutes
  return fresh
    && received.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}

Delivery and retries

  • Answer with any 2xx within 8 seconds. Redirects are not followed.
  • Network errors, 5xx and 429 are retried up to 4 attempts in total, a fraction of a second apart. Other 4xx responses are not retried.
  • After 15 failed deliveries in a row the subscription is disabled. GET /webhooks shows its status and failureCount, and GET /webhooks/{id}/deliveries shows each attempt.
  • A subscription stops receiving when the key that created it is revoked or expires, and while agent access is paused.

When your plan changes

SubscriptionWhat the API allows
Workspace created, no card added yetReads only (subscription_required). Add a card in Billing to start the free trial.
Trial running (card on file)Everything your role and scopes allow.
Trial cancelled, before it endsEverything, until the trial ends. No charge.
Paid and activeEverything your role and scopes allow.
Payment failed, first 7 daysEverything, for 7 days from the first failed payment. The app shows a banner with the date.
Payment still failed after 7 daysRead-only. Writes return entitlement_blocked with reason subscription_inactive.
Cancelled, before the end of the paid periodEverything, until the paid-through date.
Cancelled, after the paid periodRead-only (subscription_inactive).
Trial ended without a plan (cancelled, or an older trial without a card)Reads only (subscription_inactive or trial_expired).
No subscription yetReads only (subscription_required).

Blocked writes return 402 entitlement_blocked with a reason. Reads, cancelling queued posts (POST /publish-jobs/{id}/cancel), deleting drafts, sub-tokens and webhook management keep working, and an owner can export the workspace’s data from the dashboard.

Errors

Errors use RFC 9457 problem details with the content type application/problem+json. code is stable; type links to the code’s row below; some codes add fields such as requiredScope, brandId, reason, limit or resetAt.

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "type": "https://www.rebrandos.com/docs/api#error-insufficient_scope",
  "title": "insufficient scope",
  "status": 403,
  "code": "insufficient_scope",
  "detail": "This key lacks the publish:execute scope.",
  "requiredScope": "publish:execute",
  "hint": "Recreate the key with the needed scope."
}
StatusCodeMeaning
400brand_requiredThe request needs a brand. Send brand in the body (drafts) or brandId in the query.
400draft_requiredPOST /publish needs a draftId.
400channel_requiredThe draft has no channel. Send channel with the publish request.
400invalid_scheduled_atscheduledAt is not an ISO 8601 date-time with a time zone.
400scheduled_at_in_pastscheduledAt must be in the future.
400draft_update_failedThe draft could not be updated. detail says why.
400unsafe_urlWebhook URLs must be https on a public host. Private, loopback and link-local addresses are refused.
400webhook_invalidThe webhook could not be created. events lists the event names you can subscribe to.
400at_least_one_scope_requiredA sub-token needs at least one scope.
400scope_exceeds_parentA sub-token asked for a scope its parent key does not hold.
400brand_exceeds_parentA sub-token asked for a brand its parent key cannot reach.
400ip_allowlist_exceeds_parentA sub-token’s IP allowlist must sit inside its parent’s allowlist.
400cannot_derive_from_subtokenSub-tokens cannot mint their own sub-tokens. Derive from the parent key.
400expires_at_must_be_futureA sub-token’s expiresAt must be in the future.
400expires_at_invalidA sub-token’s expiresAt is not a valid ISO 8601 date-time.
400agent_key_scope_invalidA sub-token asked for a scope that does not exist. scopes lists them.
400parent_expiredThe parent key has expired, so it cannot mint sub-tokens.
400parent_rotatedThe parent key was rotated and its grace period has ended.
400keys_killedAgent access is paused for the workspace, so no sub-tokens can be minted.
401missing_keyNo Authorization: Bearer header was sent.
401invalid_keyThe value is not a well-formed ReBrandOS key (prefix or checksum is wrong).
401not_foundThe key is well-formed but does not exist. (not_found with status 404 means the endpoint does not exist.)
401revokedThe key was revoked, by a person or by secret scanning.
401expiredThe key is past its expiry date. Create a new one.
401rotatedThe key was rotated and its grace period has ended. Use the new key.
401restriction_invalidThe key’s stored restrictions could not be read, so it fails closed. Create a new key.
401parent_revokedThis sub-token’s parent key was revoked, rotated or expired.
402entitlement_blockedThe workspace’s plan does not allow this write right now. reason is trial_expired, subscription_inactive or subscription_required. Reads keep working.
403killedAn admin has paused all agent access for the workspace.
403ip_not_allowedThe request came from an IP address outside the key’s allowlist.
403workspace_deletion_pendingThe workspace is scheduled for deletion, so agent access is suspended.
403insufficient_scopeThe key lacks the scope in requiredScope.
403brand_not_permittedThe key is restricted to other brands.
403issuer_no_accessThe person who created the key no longer has access to the workspace.
403issuer_cannot_writeThe person who created the key no longer has write access, so write scopes stop working.
403module_inactiveThe workspace module behind this endpoint (moduleId) is not active.
403parent_not_activeThe key used to mint a sub-token is not active.
404draft_not_foundNo draft with that id in this workspace.
404webhook_not_foundNo webhook with that id in this workspace.
404publish_job_not_foundNo publish job with that id in this workspace.
409publish_job_not_cancellableThe post has already started publishing or finished, so it can no longer be cancelled.
409idempotency_conflictThis Idempotency-Key was already used with a different request body.
409idempotency_in_progressA request with this Idempotency-Key is still running. Check its outcome before retrying.
410brandos_generation_retiredThe endpoint generated content and has been retired. Your AI client writes the content; ReBrandOS stores, checks and publishes it.
413payload_too_largeRequest bodies are limited to 1 MB.
422provider_capacity_not_readyReBrandOS could not confirm the destination account can take the post (for example it is disconnected or out of provider quota). blockers explains. Nothing was scheduled.
422publish_not_readyThe draft could not be scheduled. detail says why. Nothing was scheduled.
429rate_limitedThe key sent more requests this minute than its limit. Wait for Retry-After seconds.
429publish_cap_reachedThe key has scheduled its daily number of posts. The cap resets at 00:00 UTC (resetAt).
500internal_errorSomething failed on our side. Safe to retry reads; retry writes with the same Idempotency-Key.
503entitlement_unavailableThe plan check could not run, so nothing was done. Retry later.