NetraFlow

Batch

Submit many videos in one request and track them as a unit.

A batch runs the same capabilities on many videos and tracks them as one. It can also answer aggregate questions across the whole set. Batch sizes per plan are on Limits.

POST /v1/batch

Request body

Send urls, uploads or both.

PropTypeDescription
urlsstring[]Video links. Up to 50, within your plan's batch size across urls and uploads together.
uploadsUploadRef[]Uploaded files as { key, filename }, with keys from POST /v1/uploads/presign. Up to 50.
capabilitiesstring[]At least one of: transcription, summary, brands, metadata, custom_query, objects. Applied to every video.
queriesstring[]Questions asked of every video. Up to 10, 500 characters each, within your plan's questions per job.
objectsstring[]Things to track in every video. Up to 5.
fpsinteger (1-4)Frames looked at per second in every video. Higher catches brief appearances and costs more. Default 1, clamped to your plan's max_fps.
aggregate{ enabled, queries }Questions answered once across the whole set, for 5 credits. Send enabled: true and at least one question.
webhook_urlstringAn https URL for batch.completed and batch.paused. Starter and above.
return_proofbooleanInclude visual evidence, as on single jobs. Defaults to true.
dry_runbooleanReturn the per-video and total estimate with HTTP 200 and create nothing. Defaults to false.
max_creditsintegerRefuse the batch with BUDGET_EXCEEDED (402) if its total estimate passes this.

A batch takes no brands, surfaces, from_job or language; those fields are ignored, and brand detection finds every brand.

curl -X POST https://api.netraflow.com/v1/batch \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: sk_live_your_key_here" \
  -d '{
    "urls": [
      "https://www.youtube.com/watch?v=jNQXAC9IVRw",
      "https://www.tiktok.com/@nba/video/7672506408675314974"
    ],
    "capabilities": ["brands"],
    "aggregate": { "enabled": true, "queries": ["Which brand appears most often?"] }
  }'
{
  "data": {
    "batch_id": "batch_abcdefghij123456",
    "status": "processing",
    "total_credits_estimated": 105,
    "balance": 400,
    "would_exceed_balance": false,
    "jobs": [
      {
        "job_id": "job_aaaaaaaaaa000001",
        "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
        "platform": "youtube",
        "duration_seconds": null,
        "credits_estimated": 50,
        "status": "pending",
        "note": "duration_unresolved"
      },
      {
        "job_id": "job_bbbbbbbbbb000002",
        "url": "https://www.tiktok.com/@nba/video/7672506408675314974",
        "platform": "tiktok",
        "duration_seconds": null,
        "credits_estimated": 50,
        "status": "pending",
        "note": "duration_unresolved"
      }
    ]
  },
  "meta": { "request_id": "req_xyz789" }
}

total_credits_estimated is the jobs' estimates plus 5 credits for the aggregate answers. Those 5 are charged once the answers are written (a usage transaction with job_id: null), and not at all if no video completed.

  • would_exceed_balance is a warning: the batch is still created. A job you can't afford once its real length is known is held, and the batch paused.
  • Dry run. With dry_run: true the response holds status, total_credits_estimated, balance, would_exceed_balance and jobs, with no batch_id and no per-job job_id or status.
  • Repeats. A video listed twice, even under two links to it, runs and is charged once, and has one entry in jobs.
  • Concurrency. Videos past your plan's jobs-at-once limit wait in pending with CONCURRENT_LIMIT_REACHED and start by themselves. While they wait, a new job or batch is refused with CONCURRENT_LIMIT_REACHED (429).
  • Body size. 10 KB, counted in bytes. 50 links with 10 long questions fit; add 10 long aggregate questions and they don't. Split such a batch.

The 202 also carries total_jobs, completed_count, failed_count, credits_estimated, credits_charged, webhook_url, created_at and completed_at, kept for older clients.


GET /v1/batch/:id

curl https://api.netraflow.com/v1/batch/batch_abcdefghij123456 \
  -H "X-Api-Key: sk_live_your_key_here"
{
  "data": {
    "batch_id": "batch_abcdefghij123456",
    "status": "paused",
    "total_jobs": 10,
    "completed_count": 4,
    "failed_count": 0,
    "rollup": {
      "total": 10,
      "pending": 0,
      "processing": 0,
      "completed": 4,
      "held": 6,
      "failed": 0,
      "cancelled": 0
    },
    "credits_estimated": 500,
    "credits_charged": 112,
    "jobs": [
      {
        "job_id": "job_aaaaaaaaaa000001",
        "status": "held",
        "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
        "credits_estimated": 50,
        "reason": "insufficient_credits"
      }
    ],
    "aggregate_results": null,
    "webhook_url": null,
    "created_at": "2026-04-03T12:00:00.000Z",
    "completed_at": null
  },
  "meta": { "request_id": "req_abc456" }
}

Use rollup: completed_count and failed_count are kept for older clients and can't show held.

Batch status values

PropTypeDescription
processingstringAt least one job is pending or processing.
pausedstringNot final. Nothing left to run and at least one job held. Add credits and it resumes by itself.
completedstringEvery job ended and at least one completed. aggregate_results and credits_charged are final.
failedstringEvery job ended and none completed. The webhook still sends batch.completed.
cancelledstringCancelled with POST /v1/batch/:id/cancel. A batch whose held jobs expired is failed, not cancelled.

GET /v1/batch

Your batches, newest first. Takes status, limit (1-100, default 20) and cursor, and pages like GET /v1/jobs.

curl "https://api.netraflow.com/v1/batch?status=completed&limit=10" \
  -H "X-Api-Key: sk_live_your_key_here"
{
  "data": [
    {
      "batch_id": "batch_abcdefghij123456",
      "status": "completed",
      "total_jobs": 2,
      "completed_count": 2,
      "failed_count": 0,
      "rollup": { "total": 2, "pending": 0, "processing": 0, "completed": 2, "held": 0, "failed": 0, "cancelled": 0 },
      "credits_estimated": 105,
      "credits_charged": 30,
      "created_at": "2026-04-03T12:00:00.000Z",
      "completed_at": "2026-04-03T12:04:10.000Z"
    }
  ],
  "pagination": { "has_more": false, "next_cursor": null },
  "meta": { "request_id": "req_xyz789" }
}

Each item is a summary, with fields as on GET /v1/batch/:id, except that credits_charged stays null until the batch completes or fails (and for good on a cancelled one). GET /v1/batch/:id shows the charges so far.


POST /v1/batch/:id/cancel

Cancel a processing or paused batch and every job in it that hasn't ended. Returns HTTP 200:

{
  "data": { "batch_id": "batch_abcdefghij123456", "status": "cancelled", "cancelled_jobs": 3 },
  "meta": { "request_id": "req_xyz789" }
}
curl -X POST https://api.netraflow.com/v1/batch/batch_abcdefghij123456/cancel \
  -H "X-Api-Key: sk_live_your_key_here"

Completed jobs stay completed and charged. Cancelling again returns cancelled_jobs: 0; a batch that already completed or failed returns BATCH_NOT_CANCELLABLE (409). No webhook is sent. This is the only way to cancel a batch's jobs.


Error codes

CodeHTTPDescription
BATCH_NOT_ALLOWED403Batch is not available on your plan
TOO_MANY_URLS422urls + uploads over your plan's max_batch_size
TOO_MANY_AGGREGATE_QUERIES422Over your plan's aggregate questions per batch
VALIDATION_FAILED422aggregate missing enabled: true or a question
BATCH_NOT_FOUND404No such batch in your workspace
BATCH_NOT_CANCELLABLE409Batch already ended completed or failed
BODY_TOO_LARGE413Request body exceeds 10 KB

Every job error code applies too.

On this page