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

# Create batch

> Submit multiple profile URLs in a single request.

Submit multiple profile URLs in one request. The response returns a
`batch_id` immediately; each profile is processed independently and
delivers its own webhook.

The scan mode is selected by the path you call — every profile in the
batch runs in that mode:

| Mode  | Endpoint                   | Behaviour                                                                                                                                                     |
| ----- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Deep  | `POST /api/v2/batch/deep`  | Full analysis per profile. Each scan typically completes in under two minutes.                                                                                |
| Quick | `POST /api/v2/batch/quick` | Cheaper and faster per profile — skips link traversal, social-media scraping, and external context search. Each scan typically completes in about 30 seconds. |

Both modes accept the same request body and return the same response
shape (the `scan_mode` field echoes which one ran). See
[Scan modes](/concepts/scans-and-batches#scan-modes) for the full
comparison.

```http theme={null}
POST /api/v2/batch/deep
POST /api/v2/batch/quick
```

## Request body

<ParamField body="profile_urls" type="string[]" required>
  Array of creator profile URLs. Each URL must be a valid HTTP or
  HTTPS URL.
</ParamField>

<ParamField body="callback_url" type="string">
  HTTPS endpoint Tumban will `POST` each result to as profiles
  complete. Optional: if omitted, Tumban falls back to the
  organization's [`default_callback_url`](/api/org/update-settings).
  If neither is set, the batch still runs and individual scans can be
  polled via [`GET /api/v2/scans/{scan_id}`](/api/scans/get) — no
  webhooks are sent.
</ParamField>

<ParamField body="metadata" type="object" default="{}">
  Arbitrary JSON object applied to every profile in the batch. Echoed
  back on each per-profile webhook payload.
</ParamField>

## Response

<ResponseField name="batch_id" type="string" required>
  UUID of the batch. Use it to fetch aggregate progress via
  [`GET /api/v2/batches/{batch_id}`](/api/scans/batch-get).
</ResponseField>

<ResponseField name="status" type="string" required>
  Always `processing` on submission. See
  [Status values](/reference/status).
</ResponseField>

<ResponseField name="total_profiles" type="integer" required>
  Count of profiles accepted in the batch.
</ResponseField>

<ResponseField name="submitted_at" type="string" required>
  ISO 8601 UTC timestamp of when Tumban accepted the batch.
</ResponseField>

<ResponseField name="estimated_completion" type="string" required>
  ISO 8601 UTC timestamp of the expected completion time. Scales
  roughly linearly with batch size at the deployment's concurrency
  cap.
</ResponseField>

<ResponseField name="scan_mode" type="string" required>
  Which mode ran for every profile in the batch: `deep` or `quick`.
  Echoes the endpoint you called.
</ResponseField>

<ResponseField name="daily_limit_truncated" type="boolean">
  Present and `true` only when the batch was partially accepted because
  the org's `daily_scan_limit` had remaining capacity for fewer profiles
  than were requested. Absent (or `null`) when every submitted URL was
  accepted.
</ResponseField>

<ResponseField name="profiles_skipped" type="integer">
  Present only when `daily_limit_truncated` is `true`. The number of
  trailing profile URLs from the request that were **not** queued for
  scanning. The first `total_profiles` URLs (in submission order) were
  accepted; the last `profiles_skipped` URLs were dropped. Resubmit the
  remainder after the daily limit window resets (`00:00 UTC`).
</ResponseField>

## Example

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api-v2.tumban.com/api/v2/batch/deep \
    -H "Authorization: Bearer sk_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "profile_urls": [
        "https://creator.example/alice",
        "https://creator.example/bob",
        "https://creator.example/carol"
      ],
      "callback_url": "https://your-app.example/webhooks/tumban",
      "metadata": {"campaign": "may-sweep"}
    }'
  ```

  ```python python theme={null}
  import httpx

  response = httpx.post(
      "https://api-v2.tumban.com/api/v2/batch/deep",
      headers={"Authorization": "Bearer sk_xxx"},
      json={
          "profile_urls": [
              "https://creator.example/alice",
              "https://creator.example/bob",
              "https://creator.example/carol",
          ],
          "callback_url": "https://your-app.example/webhooks/tumban",
          "metadata": {"campaign": "may-sweep"},
      },
  )
  ```
</CodeGroup>

```json theme={null}
{
  "batch_id": "9b8a7c6d-1234-5678-9abc-def012345678",
  "status": "processing",
  "total_profiles": 3,
  "submitted_at": "2026-04-29T12:00:00.123456+00:00",
  "estimated_completion": "2026-04-29T12:06:00.123456+00:00",
  "scan_mode": "deep"
}
```

To run a quick batch instead, call `/api/v2/batch/quick` with the same
body. The response is identical except `scan_mode` is `"quick"`.

<Note>
  The batch's `scan_id`s are not returned in the response. Read each
  per-profile result via the webhook callback or by querying
  [`GET /api/v2/batches/{batch_id}`](/api/scans/batch-get) for
  aggregate progress.
</Note>

### Example — partial acceptance

When the org's `daily_scan_limit` has capacity for fewer profiles than
were requested, the batch is accepted with the leading N profiles only
and the response sets `daily_limit_truncated` and `profiles_skipped`.

**Worked example.** Submit 50 URLs with 30 remaining in the daily
quota → the **first 30** URLs (in submission order) are queued; the
**last 20** are dropped:

```json theme={null}
{
  "batch_id": "9b8a7c6d-1234-5678-9abc-def012345678",
  "status": "processing",
  "total_profiles": 30,
  "submitted_at": "2026-04-29T12:00:00.123456+00:00",
  "estimated_completion": "2026-04-29T13:00:00.123456+00:00",
  "scan_mode": "deep",
  "daily_limit_truncated": true,
  "profiles_skipped": 20
}
```

`total_profiles` is the count actually queued (30), not the count
submitted (50). To recover the skipped URLs, take the last
`profiles_skipped` entries from your original `profile_urls` array — in
the same order — and resubmit them after the next daily window opens at
`00:00 UTC`.

## Errors

| Status | Detail                                                                                                                                                                                                                                                                                                                    |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 422    | Body failed schema validation — typically a malformed URL in `profile_urls`. See [Errors → validation](/api/errors#error-envelope).                                                                                                                                                                                       |
| 429    | The org's `daily_scan_limit` is fully exhausted (no remaining capacity for even one profile). Body is structured: `{"error": "daily_scan_limit_exceeded", "limit": N, "used": N}`. See [Rate limits](/api/errors#rate-limits). A partially-fillable batch returns `200` with `daily_limit_truncated` instead — see above. |
