# 2026-10-05 — Stable API and Standard Webhooks (improvement · API, SDK, CLI · BREAKING)

> One consistent public contract. snake_case everywhere, strict request bodies, an error envelope with lowercase codes, plural paths, and webhooks that follow Standard Webhooks with new whsec_ secrets.

The public API now has the contract we intend to keep. Every change below
lands at once, and none of them is backward compatible. Update your
integration before relying on it again.

## Requests and responses

| Before                                              | Now                                                                     |
| --------------------------------------------------- | ----------------------------------------------------------------------- |
| `taskId`, `estimatedCost`, `createdAt`, ...         | `task_id`, `estimated_cost`, `created_at`, ... in every body            |
| `size.aspectRatio`                                  | `size.aspect_ratio`                                                     |
| Unknown request fields dropped                      | `400 validation_error`, naming each field                               |
| `count: 1.5` accepted                               | `count` is a whole number from 1 to 20                                  |
| A token by default, in `access.publicAccessToken`   | `generate_public_access_token: true`, returned as `public_access_token` |
| `webhook.dashboard: false`                          | `webhook.registered: false`                                             |
| `/webhook`, `/api-key`                              | `/webhooks`, `/api-keys`                                                |
| `oauthEnabled`                                      | `include_session_tasks`                                                 |
| `apiKeyIds: []` meant every key                     | `api_key_ids`: `null` or omitted is every key, `[]` is none             |
| `spendingLimit` number, period defaulted to monthly | `spending_limit` decimal string, `spending_limit_period` required       |
| A video's `url` always set                          | `url` can be `null`, like an image's. Read `url ?? mynth_url`           |
| `webhook.custom` URLs shown with 12 path characters | Shown as `scheme://host` only                                           |

`request.metadata` is yours and comes back unchanged.

## Errors

Every error is `{ "error": { "code", "message", "issues", "scopes" } }`,
including `404`, `415` and `500`. Codes are lowercase `snake_case`, in request
errors and in task failures alike: `insufficient_balance`,
`restricted_content`, `provider_error`. A call to `/webhook` or `/api-key`
answers `404 not_found` with the new path in `message`. See
[errors](/docs/api-reference/errors).

## Webhooks

Deliveries follow [Standard Webhooks](https://www.standardwebhooks.com):

- Headers are `webhook-id`, `webhook-timestamp` and `webhook-signature`
  (`v1,<base64>`). `X-Mynth-Event`, `X-Mynth-Delivery` and `X-Mynth-Signature`
  are gone.
- **Every registered webhook has a new `whsec_` secret.** Copy it from the
  endpoint's page in the [dashboard](/dashboard/webhooks) into
  `MYNTH_WEBHOOK_SECRET`. The old `wbs_` secrets no longer verify anything.
- The body is an event, `{ id, type, timestamp, data }`. `data` is the task
  exactly as `GET /tasks/{id}` returns it, and `id` equals `webhook-id`, the
  same on every retry. Deduplicate on it.
- Failed deliveries retry for about 72 hours instead of 2.5, with jitter.
- Webhook URLs must be `https` on a public host. Per-request `webhook.custom`
  URLs stay unsigned: put a token in the query string and check it.

Any Standard Webhooks library verifies a delivery. In TypeScript, update to
the new SDK, which adds `verifyWebhook()`; see
[the SDK migration](/changelog/2026-10-05-sdk-stable-api).
[Webhook payloads](/docs/api-reference/webhook-payloads) has the full format.

## CLI

`webhook create --oauth-events` is `--include-session-tasks`, and
`image generate --no-dashboard-webhooks` is `--no-registered-webhooks`.

Permalink: https://mynth.io/changelog/2026-10-05-stable-api · Markdown: https://mynth.io/changelog/2026-10-05-stable-api.md
