{
  "info": {
    "_postman_id": "5df1590b-1500-485a-8c6a-cf2b9d807adb",
    "name": "ProofLedger API v1",
    "description": "The ProofLedger API lets customers on ANY plan, including Free, submit SHA-256 hashes for anchoring on the Polygon blockchain and retrieve proof status programmatically. A public, no-auth verify endpoint is also available for any party — opposing counsel, auditors, courts — to independently confirm a proof. Every call made with an API key is limited to 120 requests per minute per key (429 + Retry-After when exceeded); GET /api/v1/proof/{id}/bundle returns a verify-proof format bundle so a proof can be checked without ProofLedger.\n\nSetup: open the collection variables and set api_key to your sk_ key (Account > API Keys, any plan). Everything else has a working default.\nRun \"Submit a SHA-256 hash for anchoring\" first; it stores proof_id for the requests that need one.\n\nReference: https://proofledger.io/api.html\nChangelog: https://proofledger.io/changelog\nAPI Terms: https://proofledger.io/api-terms\nOpenAPI: https://proofledger.io/openapi.json (this collection is generated from it)",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://proofledger.io",
      "description": "API origin. Leave as is."
    },
    {
      "key": "api_key",
      "value": "",
      "type": "secret",
      "description": "Your sk_ key from Account > API Keys (any plan, including Free). Never commit a collection export with this filled in."
    },
    {
      "key": "sha256",
      "value": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "description": "SHA-256 hex digest of the file to anchor. Hash it yourself; the file never leaves your machine."
    },
    {
      "key": "external_id",
      "value": "song-42",
      "description": "Your own record id. Re-sending the same external_id returns the existing proof instead of creating another."
    },
    {
      "key": "proof_id",
      "value": "",
      "description": "Set automatically by \"Submit a SHA-256 hash for anchoring\" (the id in its response)."
    },
    {
      "key": "webhook_id",
      "value": "",
      "description": "Set automatically by \"Register an https endpoint\"."
    },
    {
      "key": "webhook_url",
      "value": "https://example.com/proofledger/events",
      "description": "Replace with your own public https URL that accepts POST (a request bin works). The default resolves, so registration succeeds, but nothing listens there."
    },
    {
      "key": "anchored_since",
      "value": "2026-01-01T00:00:00Z",
      "description": "ISO 8601 time for the \"list proofs anchored since\" query."
    }
  ],
  "item": [
    {
      "name": "Proofs",
      "description": "Submit and retrieve blockchain-anchored timestamp proofs. Requires an API key, available on every plan including Free (25 proofs/month; PRO 500; BUSINESS 5000).",
      "item": [
        {
          "name": "Submit a SHA-256 hash for anchoring",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201\", () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));",
                  "if (pm.response.code === 200 || pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  pm.collectionVariables.set(\"proof_id\", String(body.id));",
                  "  pm.test(\"response carries the proof id\", () => pm.expect(body.id).to.exist);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/proof",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "proof"
              ]
            },
            "description": "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. 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.\n\nReference: https://proofledger.io/api.html\nNeeds the api_key collection variable.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sha256\": \"{{sha256}}\",\n  \"filename\": \"demo-mix-v3.wav\",\n  \"external_id\": \"{{external_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Submit up to 100 hashes in one call",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201\", () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  const first = (body.items || []).find(i => i.status !== \"invalid\");",
                  "  if (first && first.id) pm.collectionVariables.set(\"proof_id\", String(first.id));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/proofs/batch",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "proofs",
                "batch"
              ]
            },
            "description": "Every item is checked first. If the new items would exceed this month's API limit, nothing is recorded and the reply says how many remain (429). Otherwise 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.\n\nReference: https://proofledger.io/api.html\nNeeds the api_key collection variable.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"sha256\": \"b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3\",\n      \"external_id\": \"song-43\",\n      \"filename\": \"demo-mix-v4.wav\"\n    },\n    {\n      \"sha256\": \"c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4\",\n      \"external_id\": \"song-44\",\n      \"filename\": \"demo-mix-v5.wav\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Retrieve a proof by ID",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/proof/{{proof_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "proof",
                "{{proof_id}}"
              ]
            },
            "description": "Returns full status of a proof, including chain transaction hash, anchoring status, and public explorer URLs. Callers can only retrieve proofs they own.\n\nReference: https://proofledger.io/api.html\nNeeds the api_key collection variable."
          }
        },
        {
          "name": "Proof bundle: verify this proof without ProofLedger",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/proof/{{proof_id}}/bundle",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "proof",
                "{{proof_id}}",
                "bundle"
              ],
              "query": [
                {
                  "key": "chain",
                  "value": "polygon",
                  "description": "Which anchor fills the top-level fields. 'bitcoin' only has data on proofs anchored on Bitcoin before 2026-10-15.",
                  "disabled": true
                },
                {
                  "key": "download",
                  "value": "1",
                  "description": "Send as a file attachment.",
                  "disabled": true
                }
              ]
            },
            "description": "One JSON document with the hash, chain, transaction id and anchor time, in the verify-proof file format. Store it next to the file. Also delivered in every proof.*.anchored webhook payload as data.bundle. Owner only.\n\nReference: https://proofledger.io/api.html\nNeeds the api_key collection variable."
          }
        },
        {
          "name": "Look up by external_id, or list proofs (newest first)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/proofs",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "proofs"
              ],
              "query": [
                {
                  "key": "external_id",
                  "value": "{{external_id}}",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "anchored_since",
                  "value": "{{anchored_since}}",
                  "description": "ISO 8601, e.g. 2026-10-14T00:00:00Z",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "next_cursor from the previous page",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "",
                  "disabled": true
                }
              ]
            },
            "description": "With external_id: exactly one proof or 404. Without it: a page of the account's proofs, newest first. anchored_since limits the page to proofs whose Polygon anchor landed after that time, so an integration can catch up on what finished since its last check. cursor is the last id of the previous page.\n\nReference: https://proofledger.io/api.html\nNeeds the api_key collection variable."
          }
        },
        {
          "name": "What this account's certificates say",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/branding",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "branding"
              ]
            },
            "description": "Partner branding is approved by hand (email support@proofledger.io). Once approved, every proof the account creates afterwards carries the name as issued_via, shown on the certificate and verify page as \"Recorded through <name> · Anchored by ProofLedger\".\n\nReference: https://proofledger.io/api.html\nNeeds the api_key collection variable."
          }
        }
      ]
    },
    {
      "name": "Verification",
      "description": "Public, no-auth verification of any proof by SHA-256 hash.",
      "item": [
        {
          "name": "Verify a proof by SHA-256 hash (public)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "pm.test(\"public endpoint answers without a key\", () => pm.expect(pm.response.code).to.be.oneOf([200, 404]));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/verify?hash={{sha256}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "verify"
              ],
              "query": [
                {
                  "key": "hash",
                  "value": "{{sha256}}",
                  "description": "The 64-character SHA-256 hex digest of the file being verified. Lowercase.",
                  "disabled": false
                }
              ]
            },
            "description": "Public, no-auth endpoint. Returns whether a proof exists for the given hash, along with blockchain transaction details and links to public explorers. Designed for use by opposing counsel, auditors, expert witnesses, and any third party that needs to independently confirm a ProofLedger proof. Rate-limited to 120 requests per hour per IP.\n\nReference: https://proofledger.io/api.html\nNo API key needed.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": [
            {
              "name": "200 found",
              "status": "Verification result (whether or not a matching proof was found)",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"found\": true,\n  \"proof\": {\n    \"id\": 42,\n    \"sha256\": \"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2\",\n    \"filename\": \"site-inspection.pdf\",\n    \"size\": 0,\n    \"created_at\": \"2026-04-15T14:22:03.000Z\",\n    \"status\": \"approved\",\n    \"review_status\": \"APPROVED\",\n    \"polygon_status\": \"ANCHORED\",\n    \"bitcoin_status\": \"NOT_REQUESTED\",\n    \"evidence_readiness\": \"READY_POLYGON\",\n    \"chain_tx\": \"0xabc123...\",\n    \"bitcoin_txid\": null,\n    \"bitcoin_anchor_status\": null,\n    \"user_email\": \"a***@example.com\"\n  },\n  \"certificate_url\": \"/cert/42\",\n  \"verification_url\": \"https://proofledger.io/verify.html?hash=a1b2c3...\",\n  \"explorer_url\": \"https://polygonscan.com/tx/0xabc123...\",\n  \"bitcoin_explorer_url\": null\n}"
            },
            {
              "name": "200 notFound",
              "status": "Verification result (whether or not a matching proof was found)",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"found\": false,\n  \"sha256\": \"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Signed notifications when a proof is anchored.",
      "item": [
        {
          "name": "Register an https endpoint",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 201\", () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  pm.collectionVariables.set(\"webhook_id\", String(body.endpoint.id));",
                  "  pm.test(\"secret is shown once; store it now\", () => pm.expect(body.secret).to.be.a(\"string\"));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/webhooks",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks"
              ]
            },
            "description": "The URL must be public https; private, loopback and VPN addresses are refused. The secret is returned once. Up to 5 active endpoints per account. Events are sent only for proofs created after registration.\n\nReference: https://proofledger.io/api.html\nNeeds the api_key collection variable.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"{{webhook_url}}\",\n  \"events\": [\n    \"proof.polygon.anchored\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List endpoints (secrets never shown)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/webhooks",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks"
              ]
            },
            "description": "Reference: https://proofledger.io/api.html\nNeeds the api_key collection variable."
          }
        },
        {
          "name": "Send a signed webhook.test event now",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/webhooks/{{webhook_id}}/test",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks",
                "{{webhook_id}}",
                "test"
              ]
            },
            "description": "Reference: https://proofledger.io/api.html\nNeeds the api_key collection variable."
          }
        },
        {
          "name": "Remove an endpoint",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/webhooks/{{webhook_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Reference: https://proofledger.io/api.html\nNeeds the api_key collection variable."
          }
        }
      ]
    }
  ]
}
