---
title: "Process approved batch roster positions"
method: POST
path: "/campaigns/{id}/launch"
tags: ["Campaigns", "Human only"]
---

# Process approved batch roster positions

`POST /campaigns/{id}/launch`

## Request

- Base URL: `https://gtmswag.com/api/v1`
- URL: `https://gtmswag.com/api/v1/campaigns/{id}/launch`
- Auth: API key in cookie `gtm_session`

Human only: A signed-in teammate does this in the browser at gtmswag.com. API keys and OAuth connections receive 403 HUMAN_APPROVAL_REQUIRED with a `url` to hand to that person. Processes at most three roster positions per call from `cursor`; `nextCursor` is null once every position is done. A playbook refuses with 409 APPROVAL_REQUIRED (its gifts go through the approval queue). Paid campaigns prepare gifts on their own after payment.

## Path parameters

- `id` string, required

## Query parameters

- `cursor` integer

## Response `200`

Success: `{"data": LaunchResult}`.

- LaunchResultResponse: The success body: `{"data": LaunchResult}`.
  - `data` LaunchResult, required: One processed page of an approved batch roster; `nextCursor` is null once every position is done.
    - `sends` Send[], required
      - `id` string, required
      - `recipient` Recipient, required
        - `recipientId` string, required
        - `name` string, required
        - `email` string, required
        - `company` string, required
        - `sourceSystem` string
        - `externalId` string
        - `profileUrl` string
        - `importId` string
        - `importRowId` string
        - `addressMode` 'known' | 'confirm'
      - `campaignId` string, nullable, required
      - `status` 'awaiting_address' | 'queued' | 'in_production' | 'shipped' | 'delivered' | 'cancelled', required
      - `totalCents` integer, required
      - `testMode` boolean, required
      - `fundedBy` 'balance': balance: the gift was paid from the workspace balance when it was approved; it never needs a checkout.
      - `autoApproved` boolean: true when the playbook's auto-approve rule approved the gift rather than a person.
    - `errors` object[], required
      - `name` string, required
      - `error` string, required
    - `nextCursor` integer, nullable, required

## Other responses

- `400`: Bad request: `VALIDATION_ERROR`. Each example says when.
- `401`: Not authenticated: `AUTH_REQUIRED`. no credentials, or an OAuth token on a route that only takes workspace keys; the key is malformed, unknown or revoked.
- `403`: Forbidden: `HUMAN_APPROVAL_REQUIRED`. Each example says when.
- `404`: Not found: `NOT_FOUND`. Each example says when.
- `409`: Conflict: `APPROVAL_REQUIRED`, `INVALID_REQUEST`. Each example says when.
- `413`: Request too large: `INVALID_REQUEST`. the body exceeds 1 MiB.
- `415`: Unsupported media type: `INVALID_REQUEST`. a write with a body that is not `Content-Type: application/json` (a key request with no body needs no Content-Type).
- `429`: Too many requests: `RATE_LIMIT`. more than 240 requests in the current minute for this workspace (REST and MCP share the limit); wait `retryAfterMs`.
- `default`: Any other refusal or failure, such as 403 CSRF (a cross-origin `Origin` header), 403 HTTPS_REQUIRED or 500 INTERNAL_ERROR, with what to do next.

---

[API](https://skmtc.dev/gtmswag/apis/gtm-swag-api.md) · [All operations](https://skmtc.dev/gtmswag/apis/gtm-swag-api/llms.txt) · [OpenAPI document](https://skmtc.dev/gtmswag/apis/gtm-swag-api/revisions/9ff86fa99afe?raw)
