{"openapi":"3.1.0","info":{"title":"Migrations API","version":"1.0.0"},"paths":{"/v1/migrations":{"get":{"operationId":"migrations_listMigrations","summary":"List migrations","description":"List the org's model migrations, keyset-paginated over (created_at, id).\n\nFollows the ``/v1`` platform-API list convention (see ``customers_v1``): an\nopaque forward cursor and the ``{results, next, previous}`` envelope, so\nprogrammatic clients iterate a bounded page rather than the full set.","tags":["migrations"],"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationListPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearerAuth":[]}]},"post":{"operationId":"migrations_createMigration","summary":"Create migration","description":"Create a draft migration. Emits ``MODEL_MIGRATION_CREATED``.","tags":["migrations"],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationCreateIn"}}}},"security":[{"bearerAuth":[]}]}},"/v1/migrations/{migration_id}":{"get":{"operationId":"migrations_getMigration","summary":"Get migration","description":"Get a single migration with its most-recent events embedded, plus the\nadvisory eval-gate verdict when a suite is linked.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationDetailOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearerAuth":[]}]},"patch":{"operationId":"migrations_updateMigration","summary":"Update migration","description":"Apply ONE action: a status transition, an eval-suite (un)link, or a\nshadow-sample-rate change.\n\nA body with zero or multiple actions is a 400. Emits\n``MODEL_MIGRATION_UPDATED``.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationPatchIn"}}}},"security":[{"bearerAuth":[]}]}},"/v1/migrations/{migration_id}/abandon":{"post":{"operationId":"migrations_abandonMigration","summary":"Abandon migration","description":"Transition a migration to ``abandoned``. Emits ``MODEL_MIGRATION_ABANDONED``.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearerAuth":[]}]}},"/v1/migrations/{migration_id}/affected-policies":{"get":{"operationId":"migrations_getAffectedPolicies","summary":"Get affected policies","description":"Which routing policies would a policy_rewrite completion touch.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffectedPoliciesOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearerAuth":[]}]}},"/v1/migrations/{migration_id}/comparison":{"get":{"operationId":"migrations_getShadowComparison","summary":"Get shadow comparison","description":"Baseline-vs-candidate runtime metrics from shadow-mirror traffic.\n\nSame window scoping as the dashboard: default lower bound = current\nshadow-run start; upper bound = the cutover instant once completed/reverted;\nexplicit ``start_time`` / ``end_time`` override. Returns zeroed sides when\nES logging isn't configured, the migration never shadowed, or nothing has\nbeen mirrored yet.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"start_time","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"end_time","in":"query","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationComparisonOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearerAuth":[]}]}},"/v1/migrations/{migration_id}/comparison/requests":{"get":{"operationId":"migrations_listComparedRequests","summary":"List compared requests","description":"Per-request baseline↔candidate pairs for the current shadow run.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"start_time","in":"query","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationRequestsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearerAuth":[]}]}},"/v1/migrations/{migration_id}/complete":{"post":{"operationId":"migrations_completeMigration","summary":"Complete migration","description":"Transition a migration to ``completed`` (cutover).\n\nThe eval gate is advisory: completion always proceeds for a legal\ntransition; the gate verdict + optional override note are recorded on the\n``completed`` event. Emits ``MODEL_MIGRATION_COMPLETED``.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MigrationCompleteIn"},{"type":"null"}]}}}},"security":[{"bearerAuth":[]}]}},"/v1/migrations/{migration_id}/events":{"get":{"operationId":"migrations_listMigrationEvents","summary":"List migration events","description":"Full event trail for a migration, most-recent first.","tags":["migrations"],"parameters":[{"name":"migration_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MigrationEventOut"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"bearerAuth":[]}]}}},"tags":[{"name":"migrations"}],"servers":[{"url":"https://api-gateway.merge.dev","description":"https://api-gateway.merge.dev"}],"components":{"schemas":{"MigrationOut":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"baseline_model":{"type":"string"},"candidate_model":{"type":"string"},"status":{"type":"string"},"shadow_daily_budget_usd":{"type":["number","null"],"format":"double"},"shadow_sample_rate":{"type":"number","format":"double","default":1},"cutover_mode":{"type":"string","default":"policy_rewrite"},"created_by":{"type":["string","null"]},"experiment_suite_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","baseline_model","candidate_model","status","created_at","updated_at"],"title":"MigrationOut"},"MigrationListPage":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/MigrationOut"}},"next":{"type":["string","null"],"description":"Cursor for the next page, or null on the last page"},"previous":{"type":["string","null"],"description":"Always null; reserved"}},"required":["results","next","previous"],"description":"Model migrations, keyset-paginated. `next` is an opaque cursor for the following page; pass it back as the `cursor` query parameter.","title":"MigrationListPage"},"ValidationErrorLocItems":{"oneOf":[{"type":"string"},{"type":"integer"}],"title":"ValidationErrorLocItems"},"ValidationErrorCtx":{"type":"object","properties":{},"title":"ValidationErrorCtx"},"ValidationError":{"type":"object","properties":{"loc":{"type":"array","items":{"$ref":"#/components/schemas/ValidationErrorLocItems"}},"msg":{"type":"string"},"type":{"type":"string"},"input":{"description":"Any type"},"ctx":{"$ref":"#/components/schemas/ValidationErrorCtx"}},"required":["loc","msg","type"],"title":"ValidationError"},"HTTPValidationError":{"type":"object","properties":{"detail":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}},"title":"HTTPValidationError"},"MigrationCreateIn":{"type":"object","properties":{"baseline_model":{"type":"string","maxLength":100},"candidate_model":{"type":"string","maxLength":100},"shadow_daily_budget_usd":{"type":["number","null"],"format":"double"},"shadow_sample_rate":{"type":"number","format":"double","maximum":1,"exclusiveMinimum":0,"default":1},"experiment_suite_id":{"type":"string","format":"uuid"}},"required":["baseline_model","candidate_model","experiment_suite_id"],"title":"MigrationCreateIn"},"MigrationEventOut":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"migration_id":{"type":"string","format":"uuid"},"actor":{"type":"string"},"event":{"type":"string"},"old_status":{"type":["string","null"]},"new_status":{"type":["string","null"]},"detail":{"type":["object","null"],"additionalProperties":{"description":"Any type"}},"created_at":{"type":"string","format":"date-time"}},"required":["id","migration_id","actor","event","created_at"],"title":"MigrationEventOut"},"MigrationDetailOut":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"baseline_model":{"type":"string"},"candidate_model":{"type":"string"},"status":{"type":"string"},"shadow_daily_budget_usd":{"type":["number","null"],"format":"double"},"shadow_sample_rate":{"type":"number","format":"double","default":1},"cutover_mode":{"type":"string","default":"policy_rewrite"},"created_by":{"type":["string","null"]},"experiment_suite_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"events":{"type":"array","items":{"$ref":"#/components/schemas/MigrationEventOut"}},"eval_gate":{"type":["object","null"],"additionalProperties":{"description":"Any type"}}},"required":["id","baseline_model","candidate_model","status","created_at","updated_at"],"title":"MigrationDetailOut"},"MigrationStatus":{"type":"string","enum":["draft","shadowing","paused","completed","abandoned","reverted"],"description":"Lifecycle of a model migration.","title":"MigrationStatus"},"MigrationPatchIn":{"type":"object","properties":{"status":{"oneOf":[{"$ref":"#/components/schemas/MigrationStatus"},{"type":"null"}]},"experiment_suite_id":{"type":["string","null"],"format":"uuid"},"shadow_sample_rate":{"type":["number","null"],"format":"double","maximum":1,"exclusiveMinimum":0}},"description":"A PATCH carries ONE discrete action: a status transition, an\neval-suite (un)link, OR a shadow-sample-rate change.\n\nKeeping each PATCH to a single action keeps the emitted event trail and\nthe audit description unambiguous. A body with zero or multiple actions is\na 400. ``experiment_suite_id: null`` unlinks.","title":"MigrationPatchIn"},"AffectedPolicyOut":{"type":"object","properties":{"policy_id":{"type":"string"},"name":{"type":"string"},"scope":{"type":"string"},"is_default":{"type":"boolean","default":false},"occurrences":{"type":"object","additionalProperties":{"description":"Any type"}}},"required":["policy_id","name","scope"],"title":"AffectedPolicyOut"},"AffectedPoliciesOut":{"type":"object","properties":{"candidate_routable":{"type":"boolean","default":true},"policies":{"type":"array","items":{"$ref":"#/components/schemas/AffectedPolicyOut"}}},"description":"Live routing policies referencing the migration's baseline model —\nthe completion picker's data source. ``candidate_routable`` is org-level\n(vendor/region/ZDR/BYOK restrictions apply to the whole org).","title":"AffectedPoliciesOut"},"VariantMetricsOut":{"type":"object","properties":{"model":{"type":"string"},"request_count":{"type":"integer","default":0},"success_count":{"type":"integer","default":0},"error_count":{"type":"integer","default":0},"total_cost_usd":{"type":"number","format":"double","default":0},"avg_cost_usd":{"type":"number","format":"double","default":0},"avg_prompt_tokens":{"type":"number","format":"double","default":0},"avg_completion_tokens":{"type":"number","format":"double","default":0},"avg_total_tokens":{"type":"number","format":"double","default":0},"avg_latency_ms":{"type":"number","format":"double","default":0}},"required":["model"],"description":"Aggregated runtime metrics for one side of a migration (baseline or candidate).","title":"VariantMetricsOut"},"MigrationComparisonOut":{"type":"object","properties":{"migration_id":{"type":"string","format":"uuid"},"baseline":{"$ref":"#/components/schemas/VariantMetricsOut"},"candidate":{"$ref":"#/components/schemas/VariantMetricsOut"}},"required":["migration_id","baseline","candidate"],"description":"Side-by-side baseline vs candidate metrics from shadow-mirror traffic.\n\nThe frontend derives cost / token / latency deltas from the two sides; the\nbackend only supplies the raw aggregates so both figures share one source.","title":"MigrationComparisonOut"},"RequestSideOut":{"type":"object","properties":{"request_id":{"type":["string","null"]},"model":{"type":["string","null"]},"status":{"type":["string","null"]},"latency_ms":{"type":["number","null"],"format":"double"},"cost_usd":{"type":["number","null"],"format":"double"},"prompt_tokens":{"type":"integer","default":0},"completion_tokens":{"type":"integer","default":0},"total_tokens":{"type":"integer","default":0},"request_body":{"type":["string","null"]},"response_body":{"type":["string","null"]},"request_payload_ref":{"type":["string","null"]},"response_payload_ref":{"type":["string","null"]}},"description":"One side (baseline or candidate) of a single mirrored request.","title":"RequestSideOut"},"RequestPairOut":{"type":"object","properties":{"request_id":{"type":["string","null"]},"timestamp":{"type":["string","null"]},"baseline":{"oneOf":[{"$ref":"#/components/schemas/RequestSideOut"},{"type":"null"}]},"candidate":{"oneOf":[{"$ref":"#/components/schemas/RequestSideOut"},{"type":"null"}]}},"description":"A customer request with its shadow candidate attached (when sampled).","title":"RequestPairOut"},"MigrationRequestsOut":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RequestPairOut"}},"total":{"type":"integer","default":0},"page":{"type":"integer","default":1},"page_size":{"type":"integer","default":25},"has_next":{"type":"boolean","default":false}},"title":"MigrationRequestsOut"},"MigrationCompleteIn":{"type":"object","properties":{"override_note":{"type":["string","null"],"maxLength":1000},"policy_ids":{"type":"array","items":{"type":"string"}}},"description":"Optional completion context. The gate NEVER blocks completion; a note\nsupplied when completing against a red/missing verdict is recorded in the\nmigration's event trail.\n\n``policy_ids`` selects which routing policies get durably rewritten\nbaseline -> candidate at completion (policy_rewrite mode). An empty list\ncompletes without touching any traffic; requests pinning the baseline\ndirectly are never rewritten either way.","title":"MigrationCompleteIn"}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your gateway API key (mg_ prefix), the same key used for /v1/responses"}}}}