---
title: "Upload a large source import in parts"
method: POST
path: "/campaigns/{id}/distribution/import-chunks"
tags: ["Gift links"]
---

# Upload a large source import in parts

`POST /campaigns/{id}/distribution/import-chunks`

## Request

- Base URL: `https://gtmswag.com/api/v1`
- URL: `https://gtmswag.com/api/v1/campaigns/{id}/distribution/import-chunks`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://gtmswag.com/oauth/authorize`, token URL `https://gtmswag.com/oauth/token`, scopes: `gifts:write`)

For a source import too large for `POST /campaigns/{id}/distribution/imports`: send the UTF-8 import JSON as base64 parts (up to 384, 150,000 bytes each before encoding). Keep `uploadId`, `partCount`, `totalBytes` and each part's bytes identical on retries. `complete` turns true once every part is saved and the import is validated; while another request assembles it the response carries `retryAfterMs` (resend the same last part after it). Uploads expire after 6 hours. Accepts an MCP OAuth token with `gifts:write`.

## Path parameters

- `id` string, required

## Request body

- object
  - `uploadId` string, required: Stable upload ID; reuse it with identical metadata and part bytes on retries.
  - `partIndex` integer, required: Zero-based part position; each UTF-8 byte segment is separately base64 encoded.
  - `partCount` integer, required: Total number of parts in this upload, from 1 to 384.
  - `data` string, required: Canonical base64 for this part, at most 200000 ASCII characters; no data-URL prefix. Blank unselected cells before serializing the import JSON.
  - `totalBytes` integer, required: UTF-8 byte length of the complete serialized import JSON, at most 32 MiB.

## Response `200`

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

- SourceUploadProgressResponse: The success body: `{"data": SourceUploadProgress}`.
  - `data` SourceUploadProgress, required: Progress of a source import uploaded in parts.
    - `uploadId` string, required
    - `receivedParts` integer, required
    - `partCount` integer, required
    - `complete` boolean, required
    - `sourceImport` SourceImport: Original source columns kept with a campaign's recipients for exports.
      - `importId` string, required
      - `headers` string[], required
      - `retainedColumnIndices` integer[], required
      - `totalRows` integer, required
      - `complete` boolean, required
    - `retryAfterMs` integer

## Other responses

- `400`: Bad request: `VALIDATION_ERROR`, `UPLOAD_ENCODING_INVALID`, `INVALID_JSON`. 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: `DEMO_LIMITED`, `INSUFFICIENT_SCOPE`. Each example says when.
- `404`: Not found: `NOT_FOUND`. Each example says when.
- `409`: Conflict: `UPLOAD_CONFLICT`, `UPLOAD_SIZE_MISMATCH`, `IMPORT_CONFLICT`. Each example says when.
- `410`: Gone: `UPLOAD_EXPIRED`. Each example says when.
- `413`: Request too large: `IMPORT_SIZE_LIMIT`, `INVALID_REQUEST`. Each example says when.
- `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: `UPLOAD_CAPACITY`, `RATE_LIMIT`. Each example says when.
- `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)
