# Take a shadowing workflow live

`POST /v1/workflows/{id}/go-live`

- Authentication: required (Bearer token)
- Required scope: `api.workflows.manage`

Arms a workflow that has been running in `shadow` for real, with no
re-authoring. A workflow whose `send_mode` is not `shadow` is
`409 conflict`. On writes `{id}` resolves as a UUID or, failing that, as
the workflow's org-unique key.

In one transaction it:

- **Ends the shadow runs.** Every live run with `shadow: true` is
  cancelled with `exit_kind: "shadow_ended"`. That is what frees each
  contact's one-live-run slot so the same people can enrol for real —
  and it is why a shadow run never coexists with a live one.
- **Flips the mode** to `live` and clears the cohort and
  `shadow_started_at`.
- **Re-registers the backfills.** The `workflow_backfills` unique key is
  lifetime, so the shadow's rows are deleted first and registered again.
  The back catalogue is then enrolled for real rather than rehearsed.

The send-approval rule is applied exactly as at activation: a flow
containing a `send` statement at any depth has the gate forced ON.

A contact the shadow observed can enrol again even under
`reentry once` — the re-entry guards ignore shadow runs entirely, which
is what makes going live clean.

Classified destructive and refused for a `sk_test_` credential: it ends
in-flight journeys and turns recorded sends into real email.

## Path parameters

- `id` (string, required) — The workflow's UUID or, when the value does not parse as a UUID, its org-unique key. Unknown handles read as `404 not_found`. Accepted on every workflow route, read and write alike.

## Example request

```bash
curl -X POST 'https://api.sendops.dev/v1/workflows/string/go-live' \
  -H "Authorization: Bearer $SENDOPS_API_KEY"
```

## Responses

### 200 — The live workflow, what the shadow observed, and what going live started

Content type: `application/json`

```json
{
  "workflow": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "string",
    "key": "string",
    "description": "string",
    "status": "string",
    "invalid_reason": "string",
    "require_send_approval": true,
    "send_mode": "live",
    "shadow_started_at": "2026-05-17T20:00:00Z",
    "shadow_cohort_list_id": "00000000-0000-0000-0000-000000000000",
    "shadow_cohort": {
      "id": "00000000-0000-0000-0000-000000000000",
      "key": "string",
      "name": "string",
      "member_count": 0
    },
    "current_version": 0,
    "run_counts": {},
    "created_at": "2026-05-17T20:00:00Z",
    "updated_at": "2026-05-17T20:00:00Z",
    "source": "string",
    "content_hash": "string",
    "warnings": [
      "string"
    ],
    "diagnostics": [
      {
        "line": 0,
        "column": 0,
        "severity": "string",
        "message": "string"
      }
    ],
    "requires": [
      {
        "kind": "template",
        "key": "string",
        "status": "ok",
        "detail": "string",
        "fix": "string"
      }
    ]
  },
  "cancelled_shadow_runs": 0,
  "shadow": {
    "active": true,
    "started_at": "2026-05-17T20:00:00Z",
    "enrolled": 0,
    "would_send": 0,
    "filtered_by_consent": 0,
    "would_set_attribute": 0,
    "would_add_to_list": 0,
    "filter_reasons": {}
  },
  "require_send_approval": true,
  "send_steps": 0,
  "backfills": [
    {
      "trigger_kind": "string",
      "key": "string",
      "status": "string",
      "window_start": "2026-05-17T20:00:00Z",
      "window_end": "2026-05-17T20:00:00Z",
      "estimate": 0
    }
  ]
}
```

### 401 — Missing, malformed, or unknown API key

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 403 — Either the credential lacks the required scope (`code: invalid_scope`), or it is bound to the `test` environment and this operation is irreversible (`code: test_environment_forbidden`). Branch on `code`: the first is fixed by granting the scope, the second only by using a live credential. See the "Live and test credentials" section of the API description.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 404 — Resource not found

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 409 — The mutation is rejected by a state rule rather than a bad request. The
`code` is `conflict`.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 429 — Per-org rate limit exceeded

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 500 — Unexpected server-side failure. The `code` is `internal_error`. The
`request_id` field can be quoted to SendOps support to investigate.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```
