<!-- https://cutedyno.com/docs/changelog -->

# 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](/docs/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 `maxPostsPerDay`th 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:

```json
{
  "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](/docs/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](/docs/guides/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](/dashboard/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](/docs/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`](/docs/api/list-api-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`](/llms.txt) plus [`/llms-full.txt`](/llms-full.txt) exist for agents reading the docs directly.

The old `/agents` marketing page is gone; [MCP](/docs/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.

| Deprecated | Use instead |
| --- | --- |
| `accounts[].accountId` | `accounts[].id` |
| `state` in webhook payloads | `status` |
| Flat `{ "error": "message" }` responses | `{ "error": { "code", "message", … } }` |
| `session.url` on connect responses | `session.authUrl` |
