> ## Documentation Index
> Fetch the complete documentation index at: https://cadenya.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Swap models on variations

> Move every agent variation off one model and onto another, workspace-wide, in one call.

Models get deprecated. This is how you migrate off one without hand-editing every variation you have ever created.

The call reassigns **every** agent variation in the workspace that sits on `currentModelId`, moving it to `nextModelId`. It returns immediately and the work happens in the background.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await client.models.swap({
    workspaceId,
    modelSwaps: [
      {
        currentModelId: 'external_id:claude-sonnet-4-5',
        nextModelId: 'external_id:claude-sonnet-4-6',
      },
    ],
  });
  ```

  ```go Go theme={null}
  _, err := client.Models.Swap(ctx,
  	cadenya.ModelSwapParams{
  		WorkspaceID: cadenya.String(workspaceID),
  		ModelSwaps: []cadenya.ModelSwapParamsModelSwap{{
  			CurrentModelID: cadenya.String("external_id:claude-sonnet-4-5"),
  			NextModelID:    cadenya.String("external_id:claude-sonnet-4-6"),
  		}},
  	})
  ```

  ```ruby Ruby theme={null}
  cadenya.models.swap(
    workspace_id: workspace_id,
    model_swaps: [
      {
        current_model_id: "external_id:claude-sonnet-4-5",
        next_model_id: "external_id:claude-sonnet-4-6"
      }
    ]
  )
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.cadenya.com/v1/workspaces/${WORKSPACE_ID}/models:swapModelOnVariations" \
    -H "Authorization: Bearer ${CADENYA_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{ "modelSwaps": [
          { "currentModelId": "external_id:claude-sonnet-4-5",
            "nextModelId": "external_id:claude-sonnet-4-6" }
        ] }'
  ```
</CodeGroup>

<Warning>
  This is a workspace-wide bulk write. It touches every variation on the source model, including agents other people own and agents you forgot about. There is no dry run, no filter, and no undo beyond swapping back.

  Run [List variations](/docs/api-reference/agentvariationservice/list-variations) across your agents first, or check the model's `info` to see how many variations sit on it.
</Warning>

## It is asynchronous, and the response is empty

`SwapModelOnVariationsResponse` has no fields. No workflow ID, no count of variations moved, no handle to poll. The call returns `200` and the reassignment happens behind it.

To confirm the migration landed, read the variations back:

```typescript theme={null}
const variations = await client.agents.variations.list(agentId, { workspaceId });
for await (const v of variations) {
  console.log(v.metadata.name, v.spec.modelConfig?.modelId);
}
```

Both ID fields accept a canonical `model_...` ID or the `external_id:` form. A variation stores the canonical ID, so a variation you wrote as `external_id:claude-sonnet-4-5` still matches a swap keyed on that slug.

Calling the same swap pair twice while the first is still running is safe. The workflow ID is derived from the workspace and the two model IDs, so a duplicate call collapses into the running one and returns success rather than starting a second pass.

## Retire the old model in the same call

`disableCurrentAfterSwap` disables the source model once every variation has moved. This is the whole deprecation flow in one request.

```json theme={null}
{ "modelSwaps": [
    { "currentModelId": "external_id:claude-sonnet-4-5",
      "nextModelId": "external_id:claude-sonnet-4-6",
      "disableCurrentAfterSwap": true }
] }
```

Without it, disabling the old model separately is the safer sequence anyway, because [Disable a model](/docs/api-reference/modelservice/disable-a-model) refuses while any variation still references it:

```
POST /models/{id}:disable
-> 400  model cannot be disabled while active agent variations reference it
```

That guard is the reason this endpoint exists. It also means a disabled model can never be the state a running objective finds itself in: the system prevents the situation rather than handling it.

## What is checked before the swap starts

* Both models must resolve, by ID or `external_id:`.
* **`nextModelId` must be enabled.** Swapping onto a disabled model is a `400` on `model_swaps.next_model_id`.

Nothing else. In particular, nothing checks that the two models are compatible. A variation carrying a `compactionConfig` tuned to a 200k-token window keeps that config after moving to a model with a smaller one, and compaction triggers as a fraction of the **new** model's `maxInputTokens`.

<Note>
  A model with no `maxInputTokens` never triggers compaction at all, so a long objective on such a model grows until it fails. Check `spec.maxInputTokens` on the model you are swapping to.
</Note>

## The deprecation runbook

1. Find what is affected. List your agents' variations and group by `spec.modelConfig.modelId`.
2. Pick the replacement and read its `spec`: `maxInputTokens`, `maxOutputTokens`, and the per-million-token prices, all on the [model](/docs/api-reference/modelservice/get-a-model-by-id).
3. Swap a canary first, by editing one variation directly rather than using this endpoint.
4. Score the canary's objectives. If the [feedback](/docs/api-reference/objectiveservice/submit-feedback-for-an-objective) holds up, swap the rest.
5. Disable the old model, so nobody creates a new variation on it.

Step 3 is the one people skip. A swap rewrites the model under a prompt that was tuned for a different one, and nothing about the call tells you whether the agent still behaves.

## Related

<CardGroup cols={2}>
  <Card title="List models" icon="list" href="/docs/api-reference/modelservice/list-models">
    What your workspace offers, with context windows and pricing.
  </Card>

  <Card title="Create a variation" icon="code" href="/docs/api-reference/agentvariationservice/create-a-new-variation">
    Where `modelConfig.modelId` is set, one variation at a time.
  </Card>

  <Card title="Disable a model" icon="ban" href="/docs/api-reference/modelservice/disable-a-model">
    Refuses while variations still reference it.
  </Card>

  <Card title="Submit feedback" icon="chart-line" href="/docs/api-reference/objectiveservice/submit-feedback-for-an-objective">
    How you find out whether the new model is better.
  </Card>
</CardGroup>


## OpenAPI

````yaml post /v1/workspaces/{workspaceId}/models:swapModelOnVariations
openapi: 3.1.0
info:
  title: Cadenya API
  description: API for the Cadenya Agent Runtime platform.
  version: '1.0'
servers:
  - url: https://api.cadenya.com
    description: Production server
security:
  - bearerAuth: []
tags:
  - name: AIProviderKeyService
  - name: APIKeyService
    description: |-
      Issue, rotate, disable, and revoke a workspace's API keys. Every key
       belongs to exactly one workspace; the system-managed global account key is
       managed via GlobalAPIKeyService instead.
  - name: AccountService
    description: >-
      Manage the authenticated account. Accounts are the top-level
      organizational
       unit and contain one or more workspaces.
  - name: AgentScheduleService
    description: >-
      Manage recurring schedules attached to agents. Schedules trigger
      objectives
       on a cadence defined by AgentScheduleSpec.Schedule.
  - name: AgentService
    description: >-
      Manage AI agents within a workspace. Agents define AI behavior and tool
      access.
  - name: AgentVariationService
    description: >-
      Manage variations of an agent and their tool, sub-agent, and memory layer
      assignments.
  - name: GlobalAPIKeyService
    description: |-
      Manage the account's system-provisioned global API key. The global key is
       the only key that spans every workspace; it is created by the system and
       cannot be deleted, so the surface is retrieve, rotate, and the
       disable/enable kill switch.
  - name: MemoryService
    description: >-
      Manage memory layers and their entries. Layers are named containers that
      can
       be composed into an objective's memory cascade; entries are the keyed values
       within a layer. System-managed layers (e.g., episodic layers created by the
       runtime) cannot be mutated through this API.
  - name: ModelService
    description: |-
      Manage LLM models available to a workspace. Models represent provider and
       family pairs (e.g., "anthropic/claude-sonnet-4.6"). Workspaces are seeded
       with the supported models and you can enable or disable each one.
  - name: ObjectiveEventStreamsService
  - name: ObjectiveService
  - name: ProfilesService
    description: |-
      Operations on profiles, the account-level principals (users, API keys,
       system) that authenticate against the API.
  - name: SearchService
  - name: TenantService
    description: >-
      Read and erase tenants and the subjects under them. Tenants and subjects
      are
       created by assertion — on objective creation or widget session mint — never
       directly, so this service has no create or update: it exists to enumerate what
       assertions have produced, and to destroy it on request.
  - name: ToolService
    description: >-
      Manage tool sets and the tools they contain. Tool sets group related
      tools,
       and tools define specific capabilities available to agents.

       When a tool set is managed, only API key actors can modify its tools; human
       (profile) actors cannot.
  - name: UploadService
    description: |-
      Issue short-lived presigned URLs for direct client-to-object-storage
       uploads. Created uploads can be referenced by id when creating or updating
       resources that accept binary content (e.g., MemoryEntry).
  - name: WidgetService
    description: |-
      Manage embeddable chat widgets. A widget binds an agent to a globally
       unique hostname with a per-widget origin allowlist; browsers reach it with
       session tokens minted via WidgetSessionService.
  - name: WidgetSessionService
    description: >-
      Mint and manage widget sessions. Session creation is server-to-server
      only:
       the customer's backend authenticates its visitor, asserts tenant/subject
       context, attaches any per-visitor secrets, and receives a short-lived
       bearer token the browser uses against the widget host.
  - name: WorkspaceAdminService
    description: >-
      Administer workspaces across the account: create and archive workspaces
      and
       manage their membership. These operations are account-scoped and require the
       admin role (a token whose profile holds the WorkOS admin role); they live
       under /v1/account/workspaces rather than the workspace-scoped /v1/workspaces
       tree so an admin can manage any workspace in the account, including ones they
       are not themselves a member of.
  - name: WorkspaceSecretService
  - name: WorkspaceService
    description: |-
      Manage workspaces within an account. Workspaces provide organizational
       grouping and isolation for resources such as agents, tools, and API keys.

       This is the workspace-scoped, end-user surface. Administrative operations
       (create / archive workspaces, manage members) live in WorkspaceAdminService
       under /v1/account/workspaces and require the admin role.
paths:
  /v1/workspaces/{workspaceId}/models:swapModelOnVariations:
    post:
      tags:
        - ModelService
        - Models
      summary: Swap models on agent variations
      description: >-
        Reassigns agent variations from one model to another in bulk. Runs
        asynchronously and returns immediately.
      operationId: ModelService_SwapModelOnVariations
      parameters:
        - name: workspaceId
          in: path
          description: Workspace ID.
          required: true
          schema:
            type: string
            example: workspace_01HXKD2E5NQM3T9AYWCF133E3Q
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SwapModelOnVariationsRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SwapModelOnVariationsResponse'
        default:
          description: Default error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
      x-codeSamples:
        - lang: JavaScript
          source: >-
            import Cadenya from '@cadenya/cadenya';


            const client = new Cadenya({
              apiKey: process.env['CADENYA_API_KEY'], // This is the default and can be omitted
            });


            const response = await client.models.swap({ workspaceId:
            'workspace_01HXKD2E5NQM3T9AYWCF133E3Q' });


            console.log(response);
        - lang: Python
          source: |-
            import os
            from cadenya import Cadenya

            client = Cadenya(
                api_key=os.environ.get("CADENYA_API_KEY"),  # This is the default and can be omitted
            )
            response = client.models.swap(
                workspace_id="workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
            )
            print(response)
        - lang: Go
          source: "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"go.cadenya.com/cadenya-go\"\n\t\"go.cadenya.com/cadenya-go/option\"\n)\n\nfunc main() {\n\tclient := cadenya.NewClient(\n\t\toption.WithAPIKey(\"My API Key\"),\n\t)\n\tresponse, err := client.Models.Swap(context.TODO(), cadenya.ModelSwapParams{\n\t\tWorkspaceID: cadenya.String(\"workspace_01HXKD2E5NQM3T9AYWCF133E3Q\"),\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf(\"%+v\\n\", response)\n}\n"
        - lang: Ruby
          source: >-
            require "cadenya"


            cadenya = Cadenya::Client.new(api_key: "My API Key")


            response = cadenya.models.swap(workspace_id:
            "workspace_01HXKD2E5NQM3T9AYWCF133E3Q")


            puts(response)
        - lang: CLI
          source: |-
            cadenya models swap \
              --api-key 'My API Key' \
              --workspace-id workspace_01HXKD2E5NQM3T9AYWCF133E3Q
components:
  schemas:
    SwapModelOnVariationsRequest:
      type: object
      properties:
        workspaceId:
          readOnly: true
          example: workspace_01HXKD2E5NQM3T9AYWCF133E3Q
          type: string
          description: Workspace ID.
        modelSwaps:
          type: array
          items:
            $ref: '#/components/schemas/SwapModelOnVariationsRequest_ModelSwap'
          description: The swaps to perform.
      description: |-
        Swap model on variations request. Each ModelSwap reassigns every agent
         variation on current_model_id to next_model_id. The work runs asynchronously.
    SwapModelOnVariationsResponse:
      type: object
      properties: {}
      description: >-
        Swap model on variations response. Empty: the work runs asynchronously,
        so no
         counts are returned (a large data set would make the call slow).
    Status:
      type: object
      properties:
        code:
          type: integer
          description: >-
            The status code, which should be an enum value of
            [google.rpc.Code][google.rpc.Code].
          format: int32
        message:
          type: string
          description: >-
            A developer-facing error message, which should be in English. Any
            user-facing error message should be localized and sent in the
            [google.rpc.Status.details][google.rpc.Status.details] field, or
            localized by the client.
        details:
          type: array
          items:
            $ref: '#/components/schemas/GoogleProtobufAny'
          description: >-
            A list of messages that carry the error details.  There is a common
            set of message types for APIs to use.
      description: >-
        The `Status` type defines a logical error model that is suitable for
        different programming environments, including REST APIs and RPC APIs. It
        is used by [gRPC](https://github.com/grpc). Each `Status` message
        contains three pieces of data: error code, error message, and error
        details. You can find out more about this error model and how to work
        with it in the [API Design
        Guide](https://cloud.google.com/apis/design/errors).
    SwapModelOnVariationsRequest_ModelSwap:
      type: object
      properties:
        currentModelId:
          example: model_01HXKD2E5NQM3T9AYWCFKJ4GED
          type: string
          description: >-
            The model variations are currently on. Accepts an id or
            "external_id:" slug.
        nextModelId:
          example: model_01HXKD2E5NQM3T9AYWCFKJ4GED
          type: string
          description: >-
            The model to move variations to. Accepts an id or "external_id:"
            slug.
        disableCurrentAfterSwap:
          type: boolean
          description: Whether to disable the current model after the swap.
    GoogleProtobufAny:
      type: object
      properties:
        '@type':
          type: string
          description: The type of the serialized message.
      additionalProperties: true
      description: >-
        Contains an arbitrary serialized message along with a @type that
        describes the type of the serialized message.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````