---
title: "Submit up to 100 hashes in one call"
method: POST
path: "/api/v1/proofs/batch"
tags: ["Proofs"]
---

# Submit up to 100 hashes in one call

`POST /api/v1/proofs/batch`

## Request

- Base URL: `https://proofledger.io`
- URL: `https://proofledger.io/api/v1/proofs/batch`
- Auth: HTTP bearer

Every item is checked first. On Free, if the new items would pass this month's 100, nothing is recorded and the reply says how many remain (429). On Developer and Platform, proofs past the allowance are recorded and billed at $0.01 each. The new proofs are written in one transaction and anchoring starts. An item whose external_id already exists on the account comes back as `exists` with the existing proof and costs nothing.

## Headers

- `Idempotency-Key` string

## Request body

- BatchRequest
  - `items` object[], required
    - `sha256` string, required
    - `external_id` string
    - `filename` string

## Response `200`

Nothing new to create (every valid item already existed).

- BatchResponse
  - `batch_id` string, nullable
  - `summary` object
    - `received` integer
    - `created` integer
    - `exists` integer
    - `invalid` integer
  - `monthly_limit` integer
  - `used_this_month` integer
  - `remaining` integer
  - `overage` integer: How many proofs in this batch were past the plan's monthly allowance and billed at $0.01 each (Developer and Platform; always 0 on Free).
  - `items` object[]
    - `index` integer
    - `result` 'created' | 'exists' | 'invalid'
    - `error` string
    - `proof` ProofSummary: A proof as returned by the batch and lookup/list endpoints (a shorter view than GET /api/v1/proof/{id}).
      - `id` integer
      - `sha256` string
      - `external_id` string, nullable
      - `filename` string
      - `status` string
      - `polygon_status` 'PENDING' | 'ANCHORED'
      - `bitcoin_status` 'NOT_REQUESTED' | 'AWAITING_PAYMENT' | 'PENDING' | 'ANCHORED' | 'FAILED': Legacy field: carries a value only on proofs anchored on Bitcoin before 2026-10-15, when Bitcoin anchoring was discontinued. Null or NOT_REQUESTED on every newer proof.
      - `created_at` string, date-time
      - `polygon_anchored_at` string, date-time, nullable: When the Polygon transaction was recorded.
      - `chain_tx` string, nullable
      - `chain_name` string, nullable
      - `bitcoin_txid` string, nullable: Legacy field: carries a value only on proofs anchored on Bitcoin before 2026-10-15, when Bitcoin anchoring was discontinued. Null on every newer proof.
      - `certificate_url` string, uri: Absolute link to the public certificate page.
      - `verification_url` string, uri: Absolute link to the public verification page for this hash.
      - `explorer_url` string, nullable
      - `already_existed` boolean: Create responses only: false on a new proof (201); true when the external_id was already recorded and the existing proof is returned (200).
      - `issued_via` string, nullable: Approved partner name copied onto the proof when it was created ("Recorded through <name> · Anchored by ProofLedger"). Null when not branded.

## Other responses

- `201`: At least one proof was created.
- `400`: The request body or parameters were invalid (e.g. hash is not 64-character hex).
- `401`: API key was missing, malformed, or does not match a known key.
- `429`: On Free (or a paid subscription that does not carry overage billing yet), the batch would pass this month's allowance; nothing was recorded.

---

[API](https://skmtc.dev/proofledger/apis/proofledger-api.md) · [All operations](https://skmtc.dev/proofledger/apis/proofledger-api/llms.txt) · [OpenAPI document](https://skmtc.dev/proofledger/apis/proofledger-api/revisions/6b9565b296a2?raw)
