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

# Campaigns

> Create, list, update, and delete campaigns, and manage campaign creators via /sdk/v1.

All endpoints below require:

* `X-Application-Id`
* `X-Api-Key`

## GET /sdk/v1/campaigns

List campaigns for your agency with optional filtering, sorting, and pagination.

### Query parameters

* `page` (default `1`)
* `limit` (default `50`)
* `search` or `query` (optional; searches campaign name)
* `sortBy` (optional)
* `sortOrder` (`asc` or `desc`)
* `status` (optional)
* `campaignStatuses` (optional; comma-separated)
* `paymentStatuses` (optional; comma-separated)
* `timeStart` and `timeEnd` (optional; ISO timestamps)
* `endingBefore` (optional; ISO timestamp)
* `endingDate` (optional; ISO timestamp)

### Response

```json theme={null}
{
  "success": true,
  "data": [
    {
      "campaignId": "camp_...",
      "campaignName": "Spring launch",
      "campaignStatus": "ONGOING",
      "startDate": "2026-03-01T00:00:00.000Z",
      "endDate": null,
      "timeZone": "America/New_York",
      "agencyTimeZone": "America/Los_Angeles",
      "effectiveTimeZone": "America/New_York"
    }
  ],
  "pagination": { "page": 1, "limit": 50, "totalPages": 1, "totalItems": 1 },
  "asOfTimestamp": "2026-03-03T12:00:00.000Z",
  "metrics": {
    "campaignCount": 1,
    "totalCreatorsCount": 5,
    "pendingValue": 0,
    "spentValue": 500,
    "committedValue": 1000,
    "totalViews": 25000,
    "totalLikes": 1200,
    "totalComments": 80,
    "totalShares": 45
  }
}
```

`timeZone` is the campaign override and may be `null`. `agencyTimeZone` is the
agency default and may also be `null`. `effectiveTimeZone` is always present
and resolves in this order: campaign timezone, agency timezone, then `UTC`.

## POST /sdk/v1/campaigns

Create a campaign. Campaigns are created as **active** (`ONGOING`).

### Body

```json theme={null}
{
  "campaignName": "Summer campaign",
  "startDate": "2026-06-01",
  "endDate": "2026-08-31",
  "campaignDescription": "Influencer campaign for summer product line",
  "brandId": "brand_...",
  "timeZone": "America/New_York",
  "activate": true
}
```

<ParamField body="campaignName" type="string" required>
  Campaign name. You can also use `campaignTitle` as an alias.
</ParamField>

<ParamField body="startDate" type="string" required>
  Campaign start date (ISO format).
</ParamField>

<ParamField body="endDate" type="string">
  Campaign end date (ISO format). Omit for open-ended campaigns.
</ParamField>

<ParamField body="campaignDescription" type="string">
  Free-text description.
</ParamField>

<ParamField body="brandId" type="string">
  Associate the campaign with an existing brand.
</ParamField>

<ParamField body="timeZone" type="string">
  Optional IANA timezone name, such as `America/New_York` or `Europe/Paris`.
  Omit it or send `null` to inherit the agency timezone. Use the
  [timezone catalog](/timezones) to get accepted names and display labels.
</ParamField>

<ParamField body="activate" type="boolean" default="true">
  Optional. If provided, this must be `true`. `false` is not supported in SDK campaign creation.
</ParamField>

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "campaignId": "camp_...",
    "campaignStatus": "ONGOING",
    "timeZone": "America/New_York",
    "agencyTimeZone": "America/Los_Angeles",
    "effectiveTimeZone": "America/New_York"
  }
}
```

### Common errors

* `400`: missing `campaignName` or `startDate`
* `400`: `activate` is invalid or set to `false`
* `400`: `timeZone` is not a valid IANA timezone

## GET /sdk/v1/campaigns/:campaignId

Fetch a single campaign with its details and high-level analytics in one response.

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "campaign": {
      "campaignId": "camp_...",
      "campaignName": "Summer campaign",
      "campaignStatus": "ONGOING",
      "startDate": "2026-06-01T00:00:00.000Z",
      "endDate": "2026-08-31T00:00:00.000Z",
      "campaignDescription": "Influencer campaign for summer product line",
      "timeZone": "America/New_York",
      "agencyTimeZone": "America/Los_Angeles",
      "effectiveTimeZone": "America/New_York"
    },
    "analytics": {
      "totalCreators": 5,
      "totalViews": 25000,
      "totalLikes": 1200,
      "totalComments": 80,
      "totalShares": 45
    }
  },
  "asOfTimestamp": "2026-03-03T12:00:00.000Z"
}
```

<Note>
  `asOfTimestamp` indicates when media tracking metrics were last refreshed. It may be `null` if no media has been tracked yet.
</Note>

### Common errors

* `404`: campaign not found or does not belong to your agency

## PATCH /sdk/v1/campaigns/:campaignId

Update a campaign. All fields are optional; only the fields you include are changed.

### Body

```json theme={null}
{
  "campaignName": "Updated campaign name",
  "startDate": "2026-06-15",
  "endDate": "2026-09-30",
  "campaignDescription": "Revised description",
  "brandId": "brand_...",
  "timeZone": "Europe/Paris"
}
```

<ParamField body="timeZone" type="string | null">
  IANA timezone name for the campaign. Send `null` to clear the override and
  inherit the agency timezone. Use the [timezone catalog](/timezones) to get
  accepted names and display labels.
</ParamField>

Set `endDate`, `campaignDescription`, `brandId`, or `timeZone` to `null` to
clear it. When an effective timezone changes, Grade realigns future calendar
pay-cycle dates for campaign creators.

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "campaignId": "camp_...",
    "updated": true,
    "timeZone": "Europe/Paris",
    "agencyTimeZone": "America/Los_Angeles",
    "effectiveTimeZone": "Europe/Paris"
  }
}
```

### Common errors

* `400`: no updatable field was provided or `timeZone` is invalid
* `404`: campaign not found or does not belong to your agency

## DELETE /sdk/v1/campaigns/:campaignId

Soft-delete a campaign.

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "campaignId": "camp_...",
    "deleted": true
  }
}
```

### Common errors

* `404`: campaign not found or does not belong to your agency
* `409`: cannot delete because payouts already exist for this campaign

***

## Campaign creators

Campaign creators are identified by email. You can manage membership explicitly with the endpoints below, but it is **optional** — calling media or contract upsert endpoints with an email automatically adds the creator to the campaign if they are not already a member.

## GET /sdk/v2/campaigns/:campaignId/creators

List creators with the compact response recommended for new integrations.

See [List campaign creators (v2)](/campaign-creators/list) for the complete
field list, date-range behavior, pagination, and response examples.

### Query parameters

* `search` or `query` (optional; searches creator name/email)
* `includeHidden` (optional; `true` or `false`)
* `fields` (optional; comma-separated response fields)
* `startDate` and `endDate` (optional; `YYYY-MM-DD`, provided together)
* `page` (default `1`)
* `limit` (default `100`, maximum `500`)
* `sortBy` (optional): `creatorName`, `totalViews`, `totalLikes`, `totalComments`, `totalShares`
* `sortOrder` (`asc` or `desc`)

### Response

```json theme={null}
{
  "success": true,
  "data": [
    {
      "campaignId": "camp_...",
      "creatorEmail": "creator@example.com",
      "creatorProfileId": "cp_...",
      "campaignCreatorRecordId": "ccr_...",
      "pendingValue": 125,
      "currency": "USD",
      "totalViews": 50000
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 100,
    "total": 1,
    "totalPages": 1
  },
  "dateRange": null,
  "effectiveTimeZone": "America/New_York"
}
```

## GET /sdk/v1/campaigns/:campaignId/creators

Returns the original, larger response for existing v1 clients. Its page limit
remains `100`, it supports `cpmResultValue` sorting, and it does not use the v2
field projection.

See [List campaign creators (v1 legacy)](/campaign-creators/list-v1) for its
response fields and migration guidance.

## POST /sdk/v1/campaigns/:campaignId/creators

Add one or more creators to a campaign by email.

### Body

```json theme={null}
{
  "creatorEmail": "creator@example.com"
}
```

Bulk add example:

```json theme={null}
{
  "creatorEmails": [
    "creator1@example.com",
    "creator2@example.com"
  ]
}
```

Send either `creatorEmail` or `creatorEmails`, but not both.

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "campaignId": "camp_...",
    "creatorEmail": "creator@example.com",
    "creatorProfileId": "cp_...",
    "campaignCreatorRecordId": "ccr_..."
  }
}
```

If you send `creatorEmails`, the response is batched:

```json theme={null}
{
  "success": true,
  "successCount": 2,
  "failureCount": 0,
  "results": [
    {
      "campaignId": "camp_...",
      "creatorEmail": "creator1@example.com",
      "creatorProfileId": "cp_...",
      "campaignCreatorRecordId": "ccr_...",
      "added": true
    }
  ]
}
```

### Common errors

* `400`: missing or invalid `creatorEmail`
* `400`: invalid `creatorEmails` payload

<Note>
  The former bulk contract upsert is deprecated. Add creators first, then read
  and write each creator's unified contract through the
  `campaignCreatorRecordId` contract-history endpoints. Contract writes require
  a complete contract and `basedOnVersionId`.
</Note>

## DELETE /sdk/v1/campaigns/:campaignId/creators

Remove a creator from a campaign.

### Query parameters

* `email` (required)

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "campaignId": "camp_...",
    "creatorEmail": "creator@example.com",
    "campaignCreatorRecordId": "ccr_...",
    "removed": true
  }
}
```

### Common errors

* `400`: missing `email`
* `404`: creator not found in this campaign
* `409`: cannot remove creator because payments exist
