Reference

Changelog

What changed in the API, and what you need to do about it.

Breaking changes get a new version. Everything below is additive or a deprecation with the old behaviour still working, so no action is required unless a line says so.

Subscribe to ["*"] on a webhook endpoint in staging if you want to see new event types as they ship — but not in production, where a new event arriving in an unprepared handler is a way to break things.

2026-07 — Multi-tenancy, one contract, and something to install

The largest release so far. If you integrated before this, everything you wrote still works.

Profiles and programmatic keys

  • GET/POST /v1/profiles and GET/PATCH/DELETE /v1/profiles/:id — create and manage one profile per customer without the dashboard.
  • GET/POST /v1/api-keys and DELETE /v1/api-keys/:id — mint keys scoped to specific profiles, with scope, expiresIn, allowedAccountIds, maxPostsPerDay, and requireApproval.
  • profileId accepted on every endpoint that touches customer data — a query parameter on reads, a body field on writes.
  • profileId included in every webhook payload, so one endpoint can route to the right customer.

An existing key keeps working exactly as before, scoped to the one profile it was created in. See Build a platform.

Restrictions that are actually enforced

allowedAccountIds, maxPostsPerDay, and requireApproval were stored but never checked. They are now enforced on every write:

  • A post naming an account outside allowedAccountIds returns account_not_allowed.
  • The maxPostsPerDayth post in a UTC day returns daily_post_limit.
  • A key or profile with requireApproval routes posts to pending_approval regardless of publishNow, and an agent-scope key cannot nominate its own approver.
  • A profile's allowedPlatforms returns platform_not_allowed for accounts on other platforms.

If you set any of these fields expecting them to be inert, they are not. Check your keys.

One error shape

Every failure from /v1 and /mcp now returns:

{
  "error": {
    "code": "accounts_not_found",
    "message": "…",
    "docsUrl": "https://cutedyno.com/docs/errors#accounts_not_found",
    "requestId": "req_…"
  }
}

There were four shapes before. The old flat { "error": "message" } is still emitted alongside for one minor version. Branch on code and you will not have to revisit this. See Errors.

Rate limit headers

RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset on every response, and Retry-After on a 429. Previously a key was limited with no signal at all. See Rate limits.

Webhook maturity

  • POST /v1/webhooks/:id/test — deliver a synthetic event with a real signature.
  • GET /v1/webhooks/:id/deliveries — the delivery log, with attempt counts and your endpoint's responses.
  • PATCH /v1/webhooks/:id — change the URL or event list, or pause delivery without losing the secret and history.
  • POST /v1/webhooks/:id/rotate-secret — issue a new signing secret.
  • A management screen at Webhooks, which the docs had promised for a while.
  • CuteDyno-Event-Id header, stable across retries, for deduplication.
  • Documented retry schedule: seven attempts over roughly 31 hours.
  • scope: 'account' subscriptions, so one endpoint receives events from every profile.
  • post.created, post.scheduled, post.cancelled, account.disconnected, approval.requested, approval.approved, and approval.rejected now actually fire. They were documented before they were delivered.

Naming

  • GET /v1/accounts returns both id (CuteDyno's) and platformAccountId (the platform's). Either is accepted anywhere an account ID is taken, so stored IDs from an earlier integration keep working.
  • accountId is now a deprecated alias of id.
  • Comment fields are camelCase, matching the rest of the API.
  • status is the field to read everywhere. state remains as a deprecated alias in webhook payloads.
  • The approval state is pending_approval in the API. It was leaking the internal in_review in some responses.
  • contentType is text, image, or video. The enum previously advertised reel, story, carousel, and slideshow, which nothing ever returned — Instagram video is a Reel and several images are a carousel without you naming the format.

Something to install

  • @cutedyno/node — typed client generated from the OpenAPI document, with retries, automatic idempotency keys, media upload, webhook verification, and pagination. See SDKs.
  • cutedyno-mcp — the MCP server as a published package, so npx -y cutedyno-mcp works. Every setup snippet in the docs previously referenced a package that did not exist.

MCP

  • 27 tools, up from 19, including get_profile, list_comments, list_profiles, create_profile, update_policy, test_webhook, and list_webhook_deliveries.
  • Loose string parameters replaced with enums, so an agent gets a validation error instead of inventing a platform name.
  • linkedin and youtube added to the connect tool.
  • Idempotency and request logging now run on /mcp, so agent calls appear in GET /v1/logs like any other client.
  • CUTEDYNO_API_URL is honoured, which the stdio server previously ignored.
  • The tool list is generated from the server's registry into a committed manifest, so the documented tools and the running tools cannot diverge.

Media

  • POST /v1/media/upload validates fileType against the accepted image and video types, returning unsupported_media_type instead of presigning an upload that no platform would accept.
  • The response carries maxBytes, and expiresIn now matches the signed URL's actual lifetime. It previously reported 15 minutes for a URL valid for an hour.

Documentation

Rebuilt from the OpenAPI document and the MCP manifest rather than hand-maintained: 13 endpoints were documented while 24 shipped, and 10 MCP tools while 19 were registered. Endpoint and tool pages are generated, guides are hand-written, and /llms.txt plus /llms-full.txt exist for agents reading the docs directly.

The old /agents marketing page is gone; MCP covers the same ground with tools that match what the server registers.

Deprecations

Still working, but going away. Nothing here has a removal date yet; a minor version will announce it.

DeprecatedUse instead
accounts[].accountIdaccounts[].id
state in webhook payloadsstatus
Flat { "error": "message" } responses{ "error": { "code", "message", … } }
session.url on connect responsessession.authUrl