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

# Campaigns API

> Create, revise, and run marketplace campaign plans.

Use the Developer Platform campaign endpoints to manage the same campaign plans as the workspace Campaigns area (`/app/{organizationId}/campaigns`). A campaign is a brief, one or more offer windows, and the offers inside those windows. Each offer carries central promotion terms. The window owns the live start and end.

## Authentication

Send an OmniCommerce API key or OAuth client-credentials token:

```http theme={null}
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json
```

| Endpoint | Required scope |
| - | - |
| `GET /api/v1/campaigns` | `promotions:read` |
| `GET /api/v1/campaigns/{campaignId}` | `promotions:read` |
| `POST /api/v1/campaigns` | `promotions:write` |
| `PUT /api/v1/campaigns/{campaignId}` | `promotions:write` |
| `POST /api/v1/campaigns/{campaignId}/actions` | `promotions:write` |
| `POST /api/v1/campaigns/archive` | `promotions:write` |
| `POST /api/v1/campaigns/delete` | `promotions:write` |
| `GET /api/v1/campaigns/options` | `promotions:write` |

Campaigns use the promotions scopes because the workspace Campaigns pages use the same promotions permissions. OAuth clients must include the scope on the client. API keys created from organization settings receive all developer scopes by default.

Session-authenticated calls must include `organizationId` in the query (GET) or JSON body (writes). Like the workspace Campaigns pages, a session member needs organization-wide access. A member limited to some stores or markets gets `403`.

## Errors

| Status | Meaning |
| - | - |
| `400` | Invalid payload, or the plan state does not allow the request. `error` says why, for example a stale `expectedRevision`. |
| `403` | Missing scope, or a session member without organization-wide access. |
| `404` | The campaign does not exist in this organization. |
| `500` | Server fault. Retry the request. |

OpenAPI reference: [API reference](/api-reference) · Spec: [openapi.json](/openapi.json)

## List campaigns

```http theme={null}
GET /api/v1/campaigns
```

### Query parameters

| Parameter | Description |
| - | - |
| `organizationId` | Organization scope for session auth. Optional for single-org OAuth/API keys. |
| `q` | Search campaign name. |
| `stage` | `all` (default) lists draft and running campaigns. Or `draft`, `approved`, `running`, `ending`, `completed`, `archived`. |
| `offset` | Zero-based offset. Default `0`. |
| `limit` | Page size. Default `20`, max `50`. |

### Response

```json theme={null}
{
  "campaigns": [],
  "total": 0,
  "hasMore": false,
  "nextOffset": 0,
  "summary": {
    "totalCount": 0,
    "stageCounts": {}
  },
  "pagination": {
    "offset": 0,
    "limit": 20,
    "total": 0,
    "hasMore": false,
    "nextOffset": null,
    "maxLimit": 50,
    "defaultLimit": 20
  }
}
```

`summary.totalCount` counts the working set (draft and running), including campaigns outside the current page. `stageCounts` counts every stage that matches `q`. The top-level `nextOffset` is the next repository offset. `pagination.nextOffset` is `null` when `hasMore` is false.

## Members and stores

```http theme={null}
GET /api/v1/campaigns/options
```

Returns the organization `members` (`id`, `name`, `email`), the connected `stores` for `brief.stores`, and the `promotionStores` for offer `promotion.targetStores`. Use a member `id` for `ownerId` and `approverId`.

## Create a draft

```http theme={null}
POST /api/v1/campaigns
```

`ownerId` and `approverId` are the user ids of organization members, from `GET /api/v1/campaigns/options`. Every store must already be connected. Stores use the marketplace, country, and store id together. Supported marketplaces are Shopee, Lazada, TikTok Shop, Shopify, WhatsApp, Zalora, Amazon, and eBay.

```bash theme={null}
curl --request POST \
  --url "https://omnicommerce.sg/api/v1/campaigns" \
  --header "Authorization: Bearer $OMNI_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "plan": {
      "brief": {
        "name": "Summer Sale",
        "ownerId": "USER_ID",
        "approverId": "USER_ID",
        "timezone": "Asia/Singapore",
        "stores": [
          { "marketplace": "shopee", "country": "SG", "storeId": "STORE_ID" }
        ]
      },
      "windows": [
        {
          "id": "launch",
          "name": "Launch week",
          "startsAt": "2026-11-01T00:00:00.000+08:00",
          "endsAt": "2026-11-08T00:00:00.000+08:00"
        }
      ],
      "offers": []
    }
  }'
```

The response is `201` with the campaign detail. A new campaign is a `draft` at revision `1`.

Offer `promotion` terms follow the [promotions](/guides/promotions) mechanic, without `status`, `startsAt`, or `endsAt`. Those dates come from the offer window. `preparesAt` is optional and must be at or before `startsAt`. The end is exclusive.

## Read and revise

```http theme={null}
GET /api/v1/campaigns/{campaignId}
PUT /api/v1/campaigns/{campaignId}
```

`expectedRevision` is required: send the revision from the last read. A draft or approved plan is replaced and returns to `draft` at the next revision. A running plan keeps executing, and the save becomes one proposed change for the approver. Ending and completed plans are locked.

A missing campaign returns `404`.

## Run a campaign

```http theme={null}
POST /api/v1/campaigns/{campaignId}/actions
```

```json theme={null}
{ "action": "approve", "expectedRevision": 1 }
```

| Action | Effect |
| - | - |
| `approve` | Mark the current plan approved. |
| `run` | Start an approved plan. |
| `approve_and_run` | Approve and start. |
| `end` | End a running campaign. |
| `resume` | Resume future windows. |
| `retry` | Retry delivery. |
| `approve_change` | Apply the proposed change to a running plan. Requires `expectedPendingVersion`. |
| `discard_change` | Drop the proposed change. |

`approve`, `run`, `approve_and_run`, `resume`, and `approve_change` must be called by the named approver. A session acts as the signed-in user id. An API key acts as `oauth:{clientId}`, so those approver actions are made from a session signed in as the approver. Archive and restore use `POST /api/v1/campaigns/archive` and do not use this action list.

## Archive, restore, and delete

```http theme={null}
POST /api/v1/campaigns/archive
```

Archive by id:

```json theme={null}
{ "action": "archive", "campaignIds": ["campaign_1"] }
```

Restore every archived campaign matching a name search, except an explicit opt-out:

```json theme={null}
{
  "action": "restore",
  "selection": {
    "mode": "all_matching",
    "filters": { "q": "Summer", "stage": "archived" },
    "excludedCampaignIds": []
  }
}
```

```http theme={null}
POST /api/v1/campaigns/delete
```

Delete forever applies only to archived campaigns.

```json theme={null}
{ "selection": { "mode": "explicit", "campaignIds": ["campaign_1"] } }
```

Both responses include `success`, `campaignIds`, `succeededCount`, `failedCount`, and ordered `results`. `success` is true only when every requested campaign succeeds.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.