{
  "entries": [
    {
      "slug": "2026-10-16-flux-3-klein",
      "date": "2026-10-16",
      "title": "FLUX.3 Klein",
      "summary": "FLUX.3 Klein is live for text-to-image and image edits.",
      "products": [
        "api",
        "sdk"
      ],
      "type": "models",
      "breaking": false,
      "models": [
        "black-forest-labs/flux-3-klein"
      ],
      "url": "https://mynth.io/changelog/2026-10-16-flux-3-klein",
      "body": "[FLUX.3 Klein](/models/black-forest-labs%2Fflux-3-klein) is available now.",
      "markdown": "https://mynth.io/changelog/2026-10-16-flux-3-klein.md"
    },
    {
      "slug": "2026-10-13-tanstack-ai-adapter",
      "date": "2026-10-13",
      "title": "TanStack AI adapter calls Mynth directly",
      "summary": "Use the adapter without the SDK, configure a gateway or custom fetch, and build model selectors with display names and capabilities.",
      "products": [
        "tanstack-ai-adapter"
      ],
      "type": "improvement",
      "breaking": true,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-13-tanstack-ai-adapter",
      "body": "The [TanStack AI adapter](/docs/sdks/integrations/tanstack-start) now calls the Mynth API\ndirectly and no longer needs `@mynthio/sdk`.\n\nConfigure `baseURL`, `defaultHeaders`, or a custom `fetch` for a gateway.\n`MYNTH_IMAGE_MODEL_META` provides model display names and capabilities, and\n`isMynthImageModel()` validates model IDs. The catalog includes Qwen Image\n2.1 Pro and Gemini Nano Banana 2.1.\n\nError classes now come from `@mynthio/tanstack-ai-adapter`. Update imports\nthat previously came from the SDK. Failed tasks throw `MynthTaskFailedError`\nwith the task's errors. See the [adapter documentation](/docs/sdks/integrations/tanstack-start)\nfor the available error classes.",
      "markdown": "https://mynth.io/changelog/2026-10-13-tanstack-ai-adapter.md"
    },
    {
      "slug": "2026-10-13-spending-limits",
      "date": "2026-10-13",
      "title": "Weekly spending limits follow ISO weeks",
      "summary": "Weekly API key spending limits reset on Monday in UTC, including across year boundaries.",
      "products": [
        "api"
      ],
      "type": "fix",
      "breaking": false,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-13-spending-limits",
      "body": "Weekly [spending limits](/docs/authentication#spending-limits) now use ISO\nweeks, which start on Monday at midnight UTC. The week stays together when\nit spans December and January.\n\nA key with no usage in the current period reports zero spent. A rejected\nreservation also keeps the period's usage counter until the period ends.",
      "markdown": "https://mynth.io/changelog/2026-10-13-spending-limits.md"
    },
    {
      "slug": "2026-10-13-dashboard",
      "date": "2026-10-13",
      "title": "Updated dashboard",
      "summary": "Manage destinations on their own pages, inspect task outputs at full size, and expand wallet transactions to see their costs.",
      "products": [],
      "type": "improvement",
      "breaking": false,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-13-dashboard",
      "body": "The [dashboard](/dashboard) has a new layout, with navigation in a sidebar\non desktop and a menu on mobile.\n\nDestinations now have separate list, create, and detail pages. Open a\ndestination to edit its settings, test an upload, or delete it.\n\nTask details show outputs at their original aspect ratio. Wallet transactions\nexpand to show the cost breakdown and a link to the task. The overview's daily\nspending chart supports keyboard navigation, and dates use your time zone.",
      "markdown": "https://mynth.io/changelog/2026-10-13-dashboard.md"
    },
    {
      "slug": "2026-10-12-video-size",
      "date": "2026-10-12",
      "title": "Aspect ratio for video",
      "summary": "Video takes a size, with the same shapes as images. The resolution tier moves into it.",
      "products": [
        "api",
        "sdk"
      ],
      "type": "feature",
      "breaking": true,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-12-video-size",
      "body": "`POST /video/generate` takes `size`, with the same shapes as\n[images](/docs/guides/generate-images#sizes). Mynth picks the closest aspect\nratio the model serves.\n\n```json\n{\n  \"model\": \"bytedance/seedance-2.0-mini\",\n  \"prompt\": \"A lighthouse beam sweeping over waves\",\n  \"size\": \"9:16_720p\"\n}\n```\n\nThe resolution tier moves into `size`, so the top-level `resolution` field is\ngone:\n\n| Before                | Now                                                                         |\n| --------------------- | --------------------------------------------------------------------------- |\n| `resolution: \"1080p\"` | `size: \"auto_1080p\"`                                                        |\n| no aspect ratio       | `size: \"9:16\"`, or `size: \"9:16_1080p\"` with a tier                         |\n| no structured size    | `size: { type: \"aspect_ratio\", aspect_ratio: \"9:16\", resolution: \"1080p\" }` |\n\nThe tier is still strict, because it sets the price. A tier the model does not\nserve answers `400 validation_error`. With a first frame, the frame sets the\nshape and the aspect ratio is ignored.\n\nEach video in the result has `aspect_ratio`, the ratio it was rendered at, or\n`null` when a first frame set it. The SDK returns it as `aspectRatio`.\n[Generate video](/docs/guides/generate-video#sizes) has the full list.",
      "markdown": "https://mynth.io/changelog/2026-10-12-video-size.md"
    },
    {
      "slug": "2026-10-12-image-input-content-type",
      "date": "2026-10-12",
      "title": "Image inputs without a content type",
      "summary": "Image editing accepts input URLs that omit the Content-Type header.",
      "products": [
        "api"
      ],
      "type": "fix",
      "breaking": false,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-12-image-input-content-type",
      "body": "Image editing through Google, DeepInfra, and Cloudflare AI now detects the\nimage format when an input URL omits its `Content-Type` header. You can use\nthese URLs without adding the header to your image server.",
      "markdown": "https://mynth.io/changelog/2026-10-12-image-input-content-type.md"
    },
    {
      "slug": "2026-10-08-public-access-token-failure",
      "date": "2026-10-08",
      "title": "A failed public access token names its task",
      "summary": "When a task is created but its public access token cannot be generated, the create now answers 500 public_access_token_failed with the task's id, instead of 201 without the token.",
      "products": [
        "api",
        "sdk"
      ],
      "type": "improvement",
      "breaking": false,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-08-public-access-token-failure",
      "body": "A create that asks for `generate_public_access_token: true` used to answer\n`201` without `public_access_token` when the token could not be generated.\nThe task was created either way, and nothing said the token was missing on\npurpose.\n\nIt now answers `500 public_access_token_failed`, with the created task's id in\n`error.task_id`:\n\n```json\n{\n  \"error\": {\n    \"code\": \"public_access_token_failed\",\n    \"message\": \"Task tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP was created and will run, but its public access token could not be generated. ...\",\n    \"task_id\": \"tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP\"\n  }\n}\n```\n\nThe task runs. Follow it from your server, by polling or a webhook, rather\nthan resending the request, which would create and charge a second task. In\nthe SDK, `MynthAPIError` carries the id as `taskId`.\n\nSee [request errors](/docs/api-reference/errors#request-errors).",
      "markdown": "https://mynth.io/changelog/2026-10-08-public-access-token-failure.md"
    },
    {
      "slug": "2026-10-07-full-api-key-and-webhook-objects",
      "date": "2026-10-07",
      "title": "API keys and webhooks return the full object",
      "summary": "Creating, listing and updating an API key return the same fields, and updating a webhook returns the whole webhook.",
      "products": [
        "api"
      ],
      "type": "improvement",
      "breaking": false,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-07-full-api-key-and-webhook-objects",
      "body": "Every API key endpoint now returns the same object: `id`, `name`,\n`key_preview`, `user_id`, `scopes`, `spending_limit`, `spending_limit_period`,\n`created_at` and `updated_at`. An unset `name` or `spending_limit` is `null`.\nCreating a key used to leave both out, and only creating one returned `user_id`.\n\n`PUT /webhooks/{id}` now returns the whole webhook, including `user_id`,\n`created_at` and `updated_at`, like `POST /webhooks` does. The signing\n`secret` is still returned only when the webhook is created.",
      "markdown": "https://mynth.io/changelog/2026-10-07-full-api-key-and-webhook-objects.md"
    },
    {
      "slug": "2026-10-07-error-code-consistency",
      "date": "2026-10-07",
      "title": "Consistent request error codes",
      "summary": "Three request errors now use the same code as the rest of the API, a 500 no longer carries internal detail, and two not-found errors gain a message.",
      "products": [
        "api"
      ],
      "type": "fix",
      "breaking": true,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-07-error-code-consistency",
      "body": "Three request error codes did not match the rest of the API:\n\n| Status | Before                          | Now                            |\n| ------ | ------------------------------- | ------------------------------ |\n| 400    | `destinations_invalid_provider` | `destination_invalid_provider` |\n| 413    | `validation_error`              | `payload_too_large`            |\n| 500    | `unknown_error`                 | `internal_server_error`        |\n\nThe `413` is an upload to `POST /image/upload` over 100 MB, and the `500` is a\nfailure to store or read a destination secret. If you branch on any of the old\ncodes, switch to the new ones.\n\nEvery `500` now has the message `Internal server error`. Some used to carry a\ndescription of the internal step that failed.\n\n`task_not_found` and `api_key_not_found` now include a `message`, like the\nother not-found errors. See [request errors](/docs/api-reference/errors#request-errors).",
      "markdown": "https://mynth.io/changelog/2026-10-07-error-code-consistency.md"
    },
    {
      "slug": "2026-10-06-qwen-image-2-1-pro",
      "date": "2026-10-06",
      "title": "Qwen Image 2.1 Pro",
      "summary": "Qwen Image 2.1 Pro is live for text-to-image and image edits.",
      "products": [
        "api",
        "sdk"
      ],
      "type": "models",
      "breaking": false,
      "models": [
        "alibaba/qwen-image-2.1-pro"
      ],
      "url": "https://mynth.io/changelog/2026-10-06-qwen-image-2-1-pro",
      "body": "[Qwen Image 2.1 Pro](/models/alibaba%2Fqwen-image-2.1-pro) is available now.",
      "markdown": "https://mynth.io/changelog/2026-10-06-qwen-image-2-1-pro.md"
    },
    {
      "slug": "2026-10-05-stable-api",
      "date": "2026-10-05",
      "title": "Stable API and Standard Webhooks",
      "summary": "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.",
      "products": [
        "api",
        "sdk",
        "cli"
      ],
      "type": "improvement",
      "breaking": true,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-05-stable-api",
      "body": "The public API now has the contract we intend to keep. Every change below\nlands at once, and none of them is backward compatible. Update your\nintegration before relying on it again.\n\n## Requests and responses\n\n| Before                                              | Now                                                                     |\n| --------------------------------------------------- | ----------------------------------------------------------------------- |\n| `taskId`, `estimatedCost`, `createdAt`, ...         | `task_id`, `estimated_cost`, `created_at`, ... in every body            |\n| `size.aspectRatio`                                  | `size.aspect_ratio`                                                     |\n| Unknown request fields dropped                      | `400 validation_error`, naming each field                               |\n| `count: 1.5` accepted                               | `count` is a whole number from 1 to 20                                  |\n| A token by default, in `access.publicAccessToken`   | `generate_public_access_token: true`, returned as `public_access_token` |\n| `webhook.dashboard: false`                          | `webhook.registered: false`                                             |\n| `/webhook`, `/api-key`                              | `/webhooks`, `/api-keys`                                                |\n| `oauthEnabled`                                      | `include_session_tasks`                                                 |\n| `apiKeyIds: []` meant every key                     | `api_key_ids`: `null` or omitted is every key, `[]` is none             |\n| `spendingLimit` number, period defaulted to monthly | `spending_limit` decimal string, `spending_limit_period` required       |\n| A video's `url` always set                          | `url` can be `null`, like an image's. Read `url ?? mynth_url`           |\n| `webhook.custom` URLs shown with 12 path characters | Shown as `scheme://host` only                                           |\n\n`request.metadata` is yours and comes back unchanged.\n\n## Errors\n\nEvery error is `{ \"error\": { \"code\", \"message\", \"issues\", \"scopes\" } }`,\nincluding `404`, `415` and `500`. Codes are lowercase `snake_case`, in request\nerrors and in task failures alike: `insufficient_balance`,\n`restricted_content`, `provider_error`. A call to `/webhook` or `/api-key`\nanswers `404 not_found` with the new path in `message`. See\n[errors](/docs/api-reference/errors).\n\n## Webhooks\n\nDeliveries follow [Standard Webhooks](https://www.standardwebhooks.com):\n\n- Headers are `webhook-id`, `webhook-timestamp` and `webhook-signature`\n  (`v1,<base64>`). `X-Mynth-Event`, `X-Mynth-Delivery` and `X-Mynth-Signature`\n  are gone.\n- **Every registered webhook has a new `whsec_` secret.** Copy it from the\n  endpoint's page in the [dashboard](/dashboard/webhooks) into\n  `MYNTH_WEBHOOK_SECRET`. The old `wbs_` secrets no longer verify anything.\n- The body is an event, `{ id, type, timestamp, data }`. `data` is the task\n  exactly as `GET /tasks/{id}` returns it, and `id` equals `webhook-id`, the\n  same on every retry. Deduplicate on it.\n- Failed deliveries retry for about 72 hours instead of 2.5, with jitter.\n- Webhook URLs must be `https` on a public host. Per-request `webhook.custom`\n  URLs stay unsigned: put a token in the query string and check it.\n\nAny Standard Webhooks library verifies a delivery. In TypeScript, update to\nthe new SDK, which adds `verifyWebhook()`; see\n[the SDK migration](/changelog/2026-10-05-sdk-stable-api).\n[Webhook payloads](/docs/api-reference/webhook-payloads) has the full format.\n\n## CLI\n\n`webhook create --oauth-events` is `--include-session-tasks`, and\n`image generate --no-dashboard-webhooks` is `--no-registered-webhooks`.",
      "markdown": "https://mynth.io/changelog/2026-10-05-stable-api.md"
    },
    {
      "slug": "2026-10-05-sdk-stable-api",
      "date": "2026-10-05",
      "title": "SDK for the stable API, with verifyWebhook",
      "summary": "The SDK follows the stable API. snake_case request fields, opt-in browser tokens, lowercase error codes, and webhook handlers built on Standard Webhooks events.",
      "products": [
        "sdk"
      ],
      "type": "improvement",
      "breaking": true,
      "version": "sdk 0.0.50",
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-05-sdk-stable-api",
      "body": "`@mynthio/sdk@0.0.50` follows the [stable API](/changelog/2026-10-05-stable-api).\nEarlier versions stop working against it, so upgrade and update your code:\n\n| Before                                          | Now                                                               |\n| ----------------------------------------------- | ----------------------------------------------------------------- |\n| `size: { type: \"aspect_ratio\", aspectRatio }`   | `size: { type: \"aspect_ratio\", aspect_ratio }`                    |\n| `access: { pat: { enabled: false } }`           | Leave it out. Tokens are off by default                           |\n| `taskAsync.access.publicAccessToken` always set | Set only with `generate_public_access_token: true` on the request |\n| `webhook: { dashboard: false }`                 | `webhook: { registered: false }`                                  |\n| `error.code === \"INSUFFICIENT_BALANCE\"`         | `error.code === \"insufficient_balance\"`                           |\n| `MynthSDKTypes.WebhookPayload`                  | `MynthSDKTypes.WebhookEvent`                                      |\n| `(result, { deliveryId })` in webhook callbacks | `(result, { event })`. Deduplicate on `event.id`                  |\n| `wbs_...` in `MYNTH_WEBHOOK_SECRET`             | The endpoint's new `whsec_...` secret, from the dashboard         |\n\nResults from polling and webhook callbacks keep their shape: `result.taskId`,\n`result.images`, `image.mynthUrl`. Raw API data the SDK passes through is\nsnake_case now: `raw.request`, the model catalog (`display_name`,\n`per_image`), and `video.estimate()` (`estimated_cost`, `estimate_kind`). A\nvideo's `url` can be `null`, and `result.urls` skips it.\n\n`verifyWebhook()` verifies a delivery outside the framework helpers and\nreturns the typed event:\n\n```ts\nimport { verifyWebhook } from \"@mynthio/sdk\";\n\nconst event = await verifyWebhook({ body: await request.text(), headers: request.headers });\n\nif (event.type === \"task.image.generate.completed\") {\n  console.log(event.id, event.data.result.images);\n}\n```\n\nThe helpers take `onEvent` for every event, including types newer than the\nSDK. They refuse unsigned deliveries to a `webhook.custom` URL unless you pass\n`{ unsigned: { verify } }`, which checks a token you put in the URL instead.\n\nSee [webhooks](/docs/concepts/webhooks) and\n[webhook payloads](/docs/api-reference/webhook-payloads).",
      "markdown": "https://mynth.io/changelog/2026-10-05-sdk-stable-api.md"
    },
    {
      "slug": "2026-10-04-webhook-task-cost",
      "date": "2026-10-04",
      "title": "Completed webhooks include task cost",
      "summary": "Read the final billed cost from task.cost on any completed task webhook.",
      "products": [
        "api"
      ],
      "type": "improvement",
      "breaking": false,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-04-webhook-task-cost",
      "body": "Every `task.*.completed` webhook now includes `task.cost`, the final USD\ncharge as a decimal string with eight decimal places:\n\n```json\n{\n  \"event\": \"task.image.generate.completed\",\n  \"task\": {\n    \"id\": \"tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP\",\n    \"cost\": \"0.03000000\"\n  }\n}\n```\n\nThis is an excerpt of the payload. The webhook still includes `request` and\n`result`. Failed task webhooks keep only `task.id` in the task object.\nSee [webhook payloads](/docs/api-reference/webhook-payloads#body).",
      "markdown": "https://mynth.io/changelog/2026-10-04-webhook-task-cost.md"
    },
    {
      "slug": "2026-10-04-validation-field-paths",
      "date": "2026-10-04",
      "title": "Validation messages include field paths",
      "summary": "Rejected requests name each invalid field in the message, including fields inside nested objects.",
      "products": [
        "api"
      ],
      "type": "improvement",
      "breaking": false,
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-04-validation-field-paths",
      "body": "The `message` on a `400 VALIDATION_ERROR` now prefixes each schema issue with\nits field path. A count above 20, for example, returns\n`count: Too big: expected number to be <=20.` Nested fields use paths such as\n`inputs[0].source.url`.\n\nThe response still includes `issues` with separate `path` and `message`\nvalues. Use `code` and `issues[].path` when handling errors in code, since the\nmessage wording can change. Unknown model IDs still suggest the closest ID.\nSee [validation errors](/docs/api-reference/errors#validation-errors).",
      "markdown": "https://mynth.io/changelog/2026-10-04-validation-field-paths.md"
    },
    {
      "slug": "2026-10-04-tanstack-ai-sdk-plain-results",
      "date": "2026-10-04",
      "title": "TanStack AI adapter supports SDK 0.0.49",
      "summary": "The TanStack AI adapter now reads the plain result objects returned by SDK 0.0.49.",
      "products": [
        "tanstack-ai-adapter"
      ],
      "type": "fix",
      "breaking": false,
      "version": "tanstack-ai-adapter 0.0.33",
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-04-tanstack-ai-sdk-plain-results",
      "body": "Upgrade to `@mynthio/tanstack-ai-adapter@0.0.33` to use SDK 0.0.49.\nThe adapter maps successful images, failed-image errors, rewritten prompts,\nand task cost from the SDK's plain result objects.\n\nSee [the TanStack AI integration](/docs/sdks/integrations/tanstack-start#tanstack-ai).",
      "markdown": "https://mynth.io/changelog/2026-10-04-tanstack-ai-sdk-plain-results.md"
    },
    {
      "slug": "2026-10-04-sdk-plain-results",
      "date": "2026-10-04",
      "title": "Plain SDK results for polling and webhooks",
      "summary": "SDK results are serializable objects with task cost, successful media, and failures; webhook callbacks receive the same result shape.",
      "products": [
        "sdk"
      ],
      "type": "improvement",
      "breaking": true,
      "version": "sdk 0.0.49",
      "models": [],
      "url": "https://mynth.io/changelog/2026-10-04-sdk-plain-results",
      "body": "Completed SDK calls now return plain objects. You can pass them to client\ncomponents, server functions, and Convex mutations, or serialize them with\n`JSON.stringify`.\n\nUpdate result access when upgrading to `@mynthio/sdk@0.0.49`:\n\n| Before                                      | Now                                                          |\n| ------------------------------------------- | ------------------------------------------------------------ |\n| `result.id`                                 | `result.taskId`                                              |\n| `result.getImages()`                        | `result.images`                                              |\n| `result.getVideos()`                        | `result.videos`                                              |\n| `result.getMetadata()`                      | `result.metadata`                                            |\n| `result.result.model`                       | `result.model`                                               |\n| `result.result.magic_prompt`                | `result.magicPrompt`                                         |\n| `result.getImages({ includeFailed: true })` | Read successful `images` and errors in `failures` separately |\n| `image.mynth_url`                           | `image.mynthUrl`                                             |\n\nImages from generation, upscaling, and background removal share `width`,\n`height`, `format`, `mimeType`, and delivery fields. `url` stays `null` when\ndelivery fails, so choose `image.url ?? image.mynthUrl` when either URL works.\n`raw.request` and `raw.result` keep the API fields before mapping.\n\nFramework webhook helpers now pass the same results as polling. Replace\n`payload.task.id` and `payload.result.images` with `result.taskId` and\n`result.images`. Failed callbacks receive a plain object with `taskId` and\n`errors`, plus request metadata when supported.\n\nResult names such as `ImageGenerationResult` are type-only exports. Remove\nconstructor calls and `instanceof` checks. Pending `TaskAsync` handles still\nuse `id` and `wait()`.\n\nSee [generating media](/docs/sdks/typescript/generating-media) and\n[webhooks](/docs/concepts/webhooks).",
      "markdown": "https://mynth.io/changelog/2026-10-04-sdk-plain-results.md"
    },
    {
      "slug": "2026-10-04-flux-3-magic-prompt",
      "date": "2026-10-04",
      "title": "Magic Prompt for FLUX.3",
      "summary": "Set magic_prompt to rewrite text-to-image prompts for FLUX.3 while preserving your text, subject counts, and layout.",
      "products": [
        "api"
      ],
      "type": "improvement",
      "breaking": false,
      "models": [
        "black-forest-labs/flux-3"
      ],
      "url": "https://mynth.io/changelog/2026-10-04-flux-3-magic-prompt",
      "body": "[FLUX.3](/models/black-forest-labs%2Fflux-3) now has model-specific prompt\nrewriting. Set `model: \"black-forest-labs/flux-3\"` and `magic_prompt: true` on\n`POST /image/generate` to use it.\n\nThe rewrite uses natural language and preserves supplied text, subject\ncounts, and layout. Your original prompt stays in `request.prompt`, and\nthe rewritten prompt is returned in `result.magic_prompt.positive`.\nSee [enhance prompts](/docs/guides/enhance-prompts).",
      "markdown": "https://mynth.io/changelog/2026-10-04-flux-3-magic-prompt.md"
    },
    {
      "slug": "2026-10-02-flux-3",
      "date": "2026-10-02",
      "title": "FLUX.3",
      "summary": "FLUX.3 is live for text-to-image and image edits.",
      "products": [
        "api",
        "sdk"
      ],
      "type": "models",
      "breaking": false,
      "models": [
        "black-forest-labs/flux-3"
      ],
      "url": "https://mynth.io/changelog/2026-10-02-flux-3",
      "body": "[FLUX.3](/models/black-forest-labs%2Fflux-3) is available now.",
      "markdown": "https://mynth.io/changelog/2026-10-02-flux-3.md"
    },
    {
      "slug": "2026-10-01-ideogram-4-5",
      "date": "2026-10-01",
      "title": "Ideogram 4.5",
      "summary": "Ideogram 4.5 is live for text-to-image and image edits.",
      "products": [
        "api",
        "sdk"
      ],
      "type": "models",
      "breaking": false,
      "models": [
        "ideogram/ideogram-4.5"
      ],
      "url": "https://mynth.io/changelog/2026-10-01-ideogram-4-5",
      "body": "[Ideogram 4.5](/models/ideogram%2Fideogram-4.5) is available now.",
      "markdown": "https://mynth.io/changelog/2026-10-01-ideogram-4-5.md"
    },
    {
      "slug": "2026-09-26-clearer-request-errors",
      "date": "2026-09-26",
      "title": "Request errors say what to fix",
      "summary": "Every rejected request now returns a code and a message you can act on, schema failures list each invalid field, and malformed requests get their own codes.",
      "products": [
        "api",
        "sdk",
        "cli"
      ],
      "type": "improvement",
      "breaking": true,
      "models": [],
      "url": "https://mynth.io/changelog/2026-09-26-clearer-request-errors",
      "body": "A request that failed validation used to come back in the validator's own shape: `{ data, error, success }` on `/image/*` and `/video/*`, `{ success, errors }` elsewhere, with no `code` and your request echoed back. Now every request error has the same body:\n\n```json\n{\n  \"code\": \"VALIDATION_ERROR\",\n  \"message\": \"model \\\"flux.2-pro\\\" is not supported. Did you mean \\\"black-forest-labs/flux.2-pro\\\"? See https://mynth.io/models, or GET /models, for the model IDs.\",\n  \"issues\": [{ \"path\": [\"model\"], \"message\": \"model \\\"flux.2-pro\\\" is not supported. …\" }]\n}\n```\n\n`message` states every problem, so it is safe to show as it is. `issues` has one `{ path, message }` per field that failed the schema. An unknown model suggests the closest id, and an invalid `size` lists the forms it takes.\n\nTwo new codes cover requests that never reach validation:\n\n- `INVALID_JSON` (`400`) for a body that is not valid JSON, with the parser's position. It used to be plain text.\n- `UNSUPPORTED_MEDIA_TYPE` (`415`) for a JSON endpoint called without `Content-Type: application/json`, or `POST /image/upload` without `multipart/form-data`. Such a body used to be read as empty, so the error blamed a missing `prompt`.\n\nA size a pinned model cannot produce, such as `16:9_4k` on a model without 4k, is now rejected at create with `400 VALIDATION_ERROR` and the size to use instead. It used to be accepted and then fail the task with `CAPABILITY_NOT_SUPPORTED`. With `model: \"auto\"` nothing changes.\n\nOther messages got more specific too: input, resolution, duration, and audio errors list what the model accepts, `UNAUTHORIZED` says what is wrong with the credential, balance and spending-limit errors state the amounts, upload errors name the file, and a `404` names the route.\n\nIf your code reads `error`, `errors`, or `data` from a `400`, read `code`, `message`, and `issues` instead. `MynthAPIError` in the SDK now carries `issues` and a useful `message`, and the CLI exits with `2` for both new codes. See [errors](/docs/api-reference/errors#validation-errors).",
      "markdown": "https://mynth.io/changelog/2026-09-26-clearer-request-errors.md"
    }
  ],
  "total": 41,
  "page": 1,
  "pageSize": 20,
  "totalPages": 3,
  "next": "https://mynth.io/changelog.json?page=2",
  "prev": null,
  "filters": {
    "q": null,
    "product": [],
    "type": [],
    "breaking": false
  },
  "options": {
    "product": [
      "api",
      "sdk",
      "cli",
      "tanstack-ai-adapter"
    ],
    "type": [
      "feature",
      "improvement",
      "fix",
      "models"
    ]
  }
}