---
title: "Run the same edits over many records"
method: POST
path: "/v1/batch"
tags: ["batch"]
---

# Run the same edits over many records

`POST /v1/batch`

## Request

- Base URL: `https://api.niubai.vip` (the document declares no server; this is the origin it was published from)
- URL: `https://api.niubai.vip/v1/batch`
- Auth: HTTP bearer

One template document, one set of edits with `{placeholders}`, many records (inline or from `records_url`): one output document per record, as a job. Answers `202` with a `job_id`; `GET /v1/jobs/{job_id}` returns one entry per record once it is done, or set `options.webhook_url`.

## Headers

- `Idempotency-Key` string

## Request body

- BatchRequest: ``POST /v1/batch``'s JSON body (spec §6). ``records`` inline xor ``records_url`` (a CSV or JSONL file fetched with the same SSRF rules as ``source.url``). The per-record count limit is ``limits.batch_max_records``, which the route applies -- it depends on the caller's plan, so it cannot be a model constraint.
  - `source` SourceModel, required
    - `url` string, uri, nullable
    - `file_id` string, nullable
    - `password` string, nullable
  - `edits` EditModel[], required
    - `op` 'replace' | 'delete' | 'redact' | 'insert' | 'form'
    - `find` string, nullable
    - `replace` string, nullable
    - `text` string, nullable
    - `at` AtModel: Where an `insert` writes: a point, or a piece of text to hang off. The point is the BASELINE ORIGIN of the first line written -- where the bottom left of the first glyph sits, not the top left of a text box. That is how every coordinate in this engine is expressed (a span's `origin`, `/v1/text`'s span geometry), so the x/y of a span read out of a document can be used here unchanged; and it is the one of the two that does not move when `fit` shrinks the size.
      - `page` integer, required
      - `x` number, nullable: Baseline origin x. Points from the page's left edge, or a fraction of the page width when units='fraction'.
      - `y` number, nullable: Baseline origin y: where the first line sits, measured from the page's TOP edge (PDF viewer convention, as every bbox and span origin in this API). A fraction of the page height when units='fraction'.
      - `after` string, nullable: Anchor text to insert after; the first occurrence on the page wins, and the style is inherited from it.
      - `units` 'pt' | 'fraction'
      - `placement` 'inline' | 'below': With `after`: 'inline' writes one space past the anchor's right edge on its own baseline; 'below' writes at the anchor line's left edge, one leading down.
    - `fields` object, nullable
    - `flatten` boolean
    - `scope` ScopeModel
      - `pages` union
        - string
        - integer[]
      - `page_labels` string[], nullable
      - `region` RegionModel
        - `x0` number, required
        - `y0` number, required
        - `x1` number, required
        - `y1` number, required
        - `units` 'fraction' | 'pt'
      - `after` string, nullable
      - `before` string, nullable
      - `occurrence` union
        - 'all' | 'first' | 'last'
        - integer
      - `span_ids` string[], nullable
      - `layers` string[]: Page layers to match in. All three are implemented; a match on the 'forms' layer is applied to the field's value, not to the page.
    - `match` MatchModel
      - `mode` 'exact' | 'icase' | 'word' | 'regex'
      - `normalize` boolean
      - `across_lines` boolean
    - `style` StyleModel
      - `font` string, nullable
      - `size` number, nullable
      - `color` string, nullable
    - `fit` 'auto' | 'reflow' | 'strict'
  - `records` object[], nullable
  - `records_url` string, uri, nullable
  - `options` BatchOptions: ``/edit``'s options plus the three only a batch has (spec §6). Like the rest of ``GatewayOptions`` these never reach the engine, so their names are in ``runner.GATEWAY_ONLY_OPTIONS`` too.
    - `dry_run` boolean
    - `strict` boolean
    - `atomic` boolean
    - `font` FontOptions
      - `policy` 'auto' | 'metric_only' | 'exact_only'
    - `collateral_threshold` integer
    - `tidy` boolean
    - `preview` 'changes' | 'pages' | 'none'
    - `redact_style` 'box' | 'text'
    - `redact_metadata` boolean
    - `redact_verify_ocr` boolean
    - `ocr` 'auto' | 'on' | 'off'
    - `ocr_lang` string[]
    - `watermark` 'auto' | 'on' | 'off'
    - `async` boolean
    - `webhook_url` string, uri, nullable: Called with POST when the job finishes: the body is the job's result (what GET /v1/jobs/{id} returns) plus `event` (job.done, job.failed, batch.done ...) and `job_id`, signed in `X-Signature` (hex HMAC-SHA256 of the raw body with the account's webhook secret). Non-2xx answers are retried after 1, 5 and 30 minutes. https only.
    - `retain` boolean
    - `break_signature` boolean
    - `output` 'zip' | 'files'
    - `filename` string, nullable
    - `fail_fast` boolean

## Response `202`

Successful Response

- unknown

## Other responses

- `400`: error.code is one of: invalid_request
- `401`: error.code is one of: unauthorized
- `402`: error.code is one of: quota_exceeded
- `403`: error.code is one of: forbidden_watermark_off
- `413`: error.code is one of: too_large
- `422`: error.code is one of: document_encrypted, document_unsupported, signed_document, edit_failed
- `429`: error.code is one of: rate_limited
- `502`: error.code is one of: upstream_error

---

[API](https://skmtc.dev/niubai/apis/pdf-edit-api-gateway.md) · [All operations](https://skmtc.dev/niubai/apis/pdf-edit-api-gateway/llms.txt) · [OpenAPI document](https://skmtc.dev/niubai/apis/pdf-edit-api-gateway/revisions/1ad0ce16ca04?raw)
