---
title: "Submit a SHA-256 hash for anchoring"
method: POST
path: "/api/v1/proof"
tags: ["Proofs"]
---

# Submit a SHA-256 hash for anchoring

`POST /api/v1/proof`

## Request

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

Submits a 64-character SHA-256 hex digest to be anchored on Polygon (usually within a minute). The file content never leaves the caller; only the hash is sent, with an optional file name and your own optional external_id. Returns immediately with a proof ID; anchoring happens asynchronously. Send your own record id as external_id and a repeat of the same request returns the existing proof (HTTP 200, already_existed: true) instead of creating another.

## Headers

- `Idempotency-Key` string

## Request body

- SubmitProofRequest
  - `sha256` string, required: 64-character SHA-256 hex digest of the file. Compute locally; file content is never sent.
  - `filename` string: Optional label (up to 500 characters). Shown on the public certificate and returned by the public verify endpoint, so keep personal details out of it. Defaults to 'api-submission'.
  - `bitcoin_requested` boolean: Ignored. Bitcoin anchoring was discontinued on 2026-10-15; every proof is anchored on Polygon. Accepted so older clients keep working.
  - `external_id` string: Your own record id for this file (private, never shown publicly). Unique per account: resending it returns the same proof.

## Response `200`

A proof with this external_id already exists on the account; it is returned unchanged with already_existed: true.

- 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`: Proof accepted and queued for anchoring.
- `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.
- `403`: API key belongs to a disabled account.
- `429`: Free has used its 100 API proofs this month (refused until the 1st). Developer and Platform continue past the allowance at $0.01 a proof; a paid subscription that does not carry overage billing yet is refused at its allowance (Developer 1,000, Platform 10,000), and the error says to email support.
- `500`: Something went wrong on our side. Retry with exponential backoff.

---

[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)
