API Reference

API Documentation

v1Operational

Everything you need to integrate Prima Evidence proof of existence into your applications.

Base URLhttps://api.primaevidence.com

Getting Started

Four steps from zero to your first blockchain proof via API.

1

Create Account

Sign up at the Developer Dashboard with your email and password.

2

Generate API Key

Create a pk_live_... key from the dashboard. Save it — shown only once.

3

Purchase Credits

Buy prepaid credits. 1 credit = 1 proof. Bulk discounts available.

4

Make API Calls

POST SHA-256 hashes to /api/v1/proofs. Permanent Arweave storage.

Quick Start

Create your first proof in under 30 seconds:

Set your credentials before running examples:

# Add to your .env or export in terminal
PRIMA_API_KEY=pk_live_YOUR_API_KEY
PRIMA_BASE_URL=https://api.primaevidence.com
importort { createHash } fromm "crypto"ypto";
importort { readFile } fromm "fs/promises"/promises";

constst API_KEY = process.env.PRIMA_API_KEY!;
constst BASE_URL = "https://api.primaevidence.com"tps://api.primaevidence.com";

// 1. Hash your file with SHA-2561. Hash your file with SHA-256
constst file = awaitit readFile("./document.pdf"document.pdf");
constst hash = createHash("sha256"a256").update(file).digest("hex"x");

// 2. Create proof via API2. Create proof via API
constst response = awaitit fetch(`${BASE_URL}/api/v1/proofs`BASE_URL}/api/v1/proofs`, {
  method: "POST"ST",
  headers: {
    "Authorization"thorization": `Bearer ${API_KEY}`arer ${API_KEY}`,
    "Content-Type"ntent-Type": "application/json"plication/json",
  },
  body: JSON.stringify({
    hash,
    metadata: { title: "document.pdf"cument.pdf" },
  }),
});

if(!response.ok) {
  constst body = awaitit response.json().catchch(() => ({}));
  throwow new Error(body.error || `HTTP ${response.status}`TP ${response.status}`);
}

constst { data } = awaitit response.json();
console.log(`Proof ID: ${data.proofId}`oof ID: ${data.proofId}`);
console.log(`Status: ${data.status}`atus: ${data.status}`);
console.log(`Credits remaining: ${data.creditsRemaining}`edits remaining: ${data.creditsRemaining}`);

Authentication

Dashboard Access

Sign in with email and password at /developer to manage API keys, purchase credits, and view usage.

API Authentication

All API requests require a valid API key passed in the Authorization header as a Bearer token.

Authorization: Bearer pk_live_<64-hex-characters>

Generate API keys from your Developer Dashboard. Keys use the format pk_live_... and are shown once at creation.

API key format is validated server-side: must match pk_live_[a-f0-9]{64}

Architecture Overview

Infrastructure

Every proof flows through a deterministic pipeline — from your application to permanent blockchain storage. Built on Cloudflare's global edge network with Durable Objects for atomic state management.

01
1

Your Application

Compute SHA-256 hash of any file or data

02
2

Prima API

Validate hash, deduct credits atomically

03
3

Durable Object

State machine with retry logic

04
4

Arweave Blockchain

Immutable, permanent storage

Technology Stack

Cloudflare WorkersEdge compute
Durable ObjectsAtomic state
Arweave via IrysPermanent storage
SHA-256Hash standard
Edge-FirstGlobal <50ms
99.9%
API Uptime SLA
<60s
Proof Confirmation
<50ms
API Response Time

Typical response times: POST /proofs ~200ms (includes atomic credit deduction), GET /proofs/:id ~100ms, GET /proofs ~150ms (scales with count). API-created proofs skip payment stages and begin at processing immediately.

Proof Lifecycle

Every proof progresses through a deterministic status pipeline. Typical confirmation time is under 60 seconds.

processing

Hash submitted, Arweave upload in progress

confirmed

Permanently stored on Arweave blockchain

arweave.net/tx_id
failed

Upload failed — credits refunded automatically

Typical confirmation: <60 seconds

Endpoints

Create a single proof of existence. Deducts 1 credit. The hash is stored permanently on Arweave blockchain.

Deducts 1 credit atomically — returns 402 if insufficient
Hash must be a valid 64-character hex SHA-256 string (case-insensitive, normalized to lowercase)
Duplicate hash returns 409 (no double-charge) — safe to retry on network timeouts
Status progresses: processing → confirmed (typically < 60 seconds)
Metadata: max 20 fields, keys ≤ 100 chars, values ≤ 500 chars
Returns X-Credits-Remaining and X-Credits-Used headers

Request Body

{
  "hash"sh": "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"fd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f",
  "metadata"tadata": {
    "title"tle": "Q4 Financial Report" Financial Report",
    "description"scription": "Quarterly earnings report SHA-256"arterly earnings report SHA-256"
  }
}

Response 200

{
  "success"ccess": truee,
  "data"ta": {
    "proofId"oofId": "a3f8c2e1-7b4d-4e6f-9a1c-3d5e7f2b8c4a"f8c2e1-7b4d-4e6f-9a1c-3d5e7f2b8c4a",
    "hash"sh": "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"fd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f",
    "status"atus": "processing"ocessing",
    "creditsUsed"editsUsed": 1    "creditsRemaining"editsRemaining": 49
    "createdAt"eatedAt": "2026-02-27T14:30:00.000Z"26-02-27T14:30:00.000Z"
  }
}

Example

curl -XPOSThttps://api.primaevidence.com/api/v1/proofs \
  -H"Authorization: Bearer pk_live_YOUR_API_KEY"orization: Bearer pk_live_YOUR_API_KEY" \
  -H"Content-Type: application/json"ent-Type: application/json" \
  -d'{
    "hash": "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f",
    "metadata": {
      "title": "Q4 Financial Report"
    }
  }'  "hash": "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f",
    "metadata": {
      "title": "Q4 Financial Report"
    }
  }'

Webhook Events

Live

Receive real-time notifications when proof status changes. Configure a webhook endpoint in your Developer Dashboard to get instant updates.

Webhook Configuration

Register your endpoint URL in the Developer Dashboard. All webhook payloads are signed with HMAC-SHA256 for verification.

POST https://your-app.com/webhooks/prima
Content-Type: application/json
X-Prima-Signature: sha256=...
proof.confirmedFired when proof is permanently stored on Arweave
{
  "event"ent": "proof.confirmed"oof.confirmed",
  "data"ta": {
    "proofId"oofId": "a3f8c2e1-7b4d-4e6f-9a1c-3d5e7f2b8c4a"f8c2e1-7b4d-4e6f-9a1c-3d5e7f2b8c4a",
    "hash"sh": "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"fd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f",
    "arweaveTransactionId"weaveTransactionId": "m787dI694Tv43-uPRDzdi6ZG..."87dI694Tv43-uPRDzdi6ZG...",
    "arweaveUrl"weaveUrl": "https://arweave.net/m787dI694Tv43..."tps://arweave.net/m787dI694Tv43...",
    "confirmedAt"nfirmedAt": "2026-02-27T14:30:45.000Z"26-02-27T14:30:45.000Z"
  },
  "timestamp"mestamp": "2026-02-27T14:30:45.000Z"26-02-27T14:30:45.000Z"
}
proof.failedFired when Arweave upload fails — credits are refunded automatically
{
  "event"ent": "proof.failed"oof.failed",
  "data"ta": {
    "proofId"oofId": "a3f8c2e1-7b4d-4e6f-9a1c-3d5e7f2b8c4a"f8c2e1-7b4d-4e6f-9a1c-3d5e7f2b8c4a",
    "hash"sh": "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"fd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f",
    "reason"ason": "Arweave upload timeout"weave upload timeout",
    "creditsRefunded"editsRefunded": 1 },
  "timestamp"mestamp": "2026-02-27T14:35:00.000Z"26-02-27T14:35:00.000Z"
}

Signature Verification

importort crypto fromm "crypto"ypto";

functionction verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  constst expected = crypto
    .createHmac("sha256"a256", secret)
    .update(payload)
    .digest("hex"x");
  returnurn crypto.timingSafeEqual(
    Buffer.fromm(signature),
    Buffer.fromm(`sha256=${expected}`a256=${expected}`)
  );
}

// Express/Hono handlerExpress/Hono handler
app.post("/webhooks/prima"ebhooks/prima", (req, res) => {
  constst signature = req.headers["x-prima-signature"prima-signature"];
  constst rawBody = JSON.stringify(req.body);

  if(!verifyWebhookSignature(rawBody, signature, WEBHOOK_SECRET)) {
    returnurn res.status(401).json({ error: "Invalid signature"valid signature" });
  }

  constst { event, data } = req.body;
  switch (event) {
    case "proof.confirmed"oof.confirmed":
      console.log(`Proof ${data.proofId} confirmed on Arweave`oof ${data.proofId} confirmed on Arweave`);
      console.log(`TX: ${data.arweaveTransactionId}`: ${data.arweaveTransactionId}`);
      breakak;
    case "proof.failed"oof.failed":
      console.log(`Proof ${data.proofId} failed: ${data.reason}`oof ${data.proofId} failed: ${data.reason}`);
      breakak;
  }
  res.json({ received: truee });
});

Webhooks are retried with exponential backoff for up to 24 hours. Your endpoint should return 2xx within 15 seconds. Failed deliveries are logged in your Developer Dashboard.

Response Headers

Every API response includes credit and rate limit information in headers.

X-Credits-RemainingYour current credit balance after this request
X-Credits-UsedCredits consumed by this request (0 for read operations)
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the rate limit window resets

Error Codes

All errors return {"success":false,"error":"..."} with the appropriate HTTP status code.

401

Invalid or missing API key

{"success":false,"error":"Valid API key required"}
402

Insufficient credits — purchase more to continue

Single: "Insufficient credits" | Batch: "Insufficient credits: need 5, have 3"
404

Resource not found — proof ID does not exist or account not initialized

{"success":false,"error":"Proof not found"}
409

Hash already exists as a proof

Single: "Hash already exists as a proof" | Batch: "Hash already exists: {hash}"
422

Validation error — check request body format

{"success":false,"error":"hash: Must be a valid SHA-256 hex string"}
429

Rate limit exceeded — retry after the indicated period

{"success":false,"error":"Too many requests"} + Retry-After header
500

Internal server error — contact support if persistent

{"success":false,"error":"Internal server error"}

Troubleshooting

Common issues and step-by-step fixes. Contact support if problems persist.

401Every request returns 401
  • Verify your key starts with pk_live_ followed by exactly 64 hex characters
  • Check the Authorization header format: Bearer pk_live_... (note the space after Bearer)
  • Ensure the key has not been revoked in the Developer Dashboard
409Hash already exists as a proof
  • This is expected idempotent behavior — you will not be double-charged
  • Use GET /api/v1/proofs to find the existing proof for this hash
  • To create a new proof, the file content must differ (producing a different SHA-256 hash)
402Insufficient credits
  • Check your balance via GET /api/v1/account or the X-Credits-Remaining response header
  • Purchase more credits at primaevidence.com/developer
  • For batch requests, your balance must cover all hashes in the batch (atomic deduction)
429Rate limit exceeded
  • Read the Retry-After response header for the number of seconds to wait
  • Implement exponential backoff in your client (see the SDK Pattern section)
  • Contact support for higher rate limits on enterprise plans

Rate Limits

Rate limits are applied per API key. Exceeding limits returns 429 with Retry-After header.

POST /api/v1/proofs60 req/min
POST /api/v1/proofs/batch10 req/min
GET /api/v1/proofs/:id300 req/min
GET /api/v1/proofs60 req/min
GET /api/v1/account600 req/min

Rate limits are tracked per API key. When exceeded, responses include a Retry-After header indicating seconds to wait. Note: POST and GET to /api/v1/proofs share the same 60/min counter. Contact support for higher limits.

Security Best Practices

Best Practices

Follow these recommendations to keep your integration secure and production-ready.

Use Environment Variables

Never hardcode API keys in source code. Use .env files locally and secure secret managers in production.

Rotate Keys Regularly

Generate new keys periodically. Revoke old keys immediately from the Developer Dashboard after rotation.

Server-Side Only

Never expose API keys in client-side code, mobile apps, or browser JavaScript. Always call the API from your backend.

HTTPS Enforced

All API endpoints enforce HTTPS. HTTP requests are rejected. TLS 1.2+ is required for all connections.

Idempotent Operations

Duplicate hash submissions return 409 Conflict. Batch operations are atomic — partial failures never occur.

Handle Errors Gracefully

Implement exponential backoff for 429 and 5xx errors. Check X-Credits-Remaining headers to prevent 402 errors.

Environment Template

# .env.local — Prima Evidence API Configurationv.local — Prima Evidence API Configuration
# Generate your API key at https://primaevidence.com/developererate your API key at https://primaevidence.com/developer

PRIMA_API_KEY=pk_live_YOUR_API_KEY_HERE
PRIMA_API_BASE=https://api.primaevidence.com
PRIMA_WEBHOOK_SECRET=whsec_YOUR_WEBHOOK_SECRET

# Optional: Override timeout (default 30s)ional: Override timeout (default 30s)
PRIMA_TIMEOUT_MS=30000

SDK Pattern

Recommended

Copy-paste client class with typed methods, error handling, and exponential backoff polling. Production-ready for any language.

// npm install @prima-evidence/sdknpm install @prima-evidence/sdk
importort { PrimaClient, PrimaApiError } fromm "@prima-evidence/sdk"rima-evidence/sdk";
importort { createHash } fromm "crypto"ypto";
importort { readFile } fromm "fs/promises"/promises";

constst prima = new PrimaClient(process.env.PRIMA_API_KEY!);

// Hash a fileHash a file
constst buffer = awaitit readFile("./contract.pdf"contract.pdf");
constst hash = createHash("sha256"a256").update(buffer).digest("hex"x");

// Create proof and wait for blockchain confirmationCreate proof and wait for blockchain confirmation
constst { proofId, creditsRemaining } = awaitit prima.createProof(hash, {
  title: "Contract v2.1"ntract v2.1",
});
console.log(`Proof created: ${proofId} (${creditsRemaining} credits left)`oof created: ${proofId} (${creditsRemaining} credits left)`);

constst confirmed = awaitit prima.waitForConfirmation(proofId);
console.log(`Arweave TX: ${confirmed.arweaveTransactionId}`weave TX: ${confirmed.arweaveTransactionId}`);

// Batch create (up to 100 proofs atomically)Batch create (up to 100 proofs atomically)
constst batch = awaitit prima.createBatch([
  { hash: "a".repeat(64, metadata: { filename: "file1.pdf"le1.pdf" } },
  { hash: "b".repeat(64, metadata: { filename: "file2.pdf"le2.pdf" } },
]);
console.log(`Batch: ${batch.count} proofs, ${batch.creditsUsed} credits used`tch: ${batch.count} proofs, ${batch.creditsUsed} credits used`);

// List proofs with paginationList proofs with pagination
constst proofs = awaitit prima.listProofs({ page: 1limit: 10 status: "confirmed"nfirmed" });
console.log(`${proofs.pagination.total} total proofs`proofs.pagination.total} total proofs`);

// Error handlingError handling
try {
  awaitit prima.createProof(hash);
} catchch (error) {
  if(error instanceof PrimaApiError) {
    console.error(`${error.status}: ${error.message}`error.status}: ${error.message}`);
    if(error.status === 402) console.error("Buy more credits"y more credits");
    if(error.status === 409) console.error("Hash already registered"sh already registered");
  }
}

Changelog

API version history and upcoming features. We follow semantic versioning with backward-compatible changes.

v1.0.02026-02-15Major Release

Initial Public Release

  • REST API with SHA-256 proof creation and batch operations (up to 100 per request)
  • Permanent Arweave blockchain storage via Irys bundler network
  • Prepaid credit system with bulk pricing tiers (up to 20% discount)
  • API key authentication with prefix-based identification and instant revocation
  • Real-time proof status tracking with webhook notifications
Coming SoonRoadmap
Webhook delivery dashboard with retry management
Official SDKs for Node.js, Python, and Go
Batch verification endpoint for proof auditing
Team accounts with role-based API key permissions