API Documentation

XRPpdf API

Programmatic PDF OCR for production workloads. Authenticate with an API key, submit PDFs, receive searchable PDFs back on a webhook or via polling. Paid in XRP — no credit cards, no chargebacks, no subscriptions.

1. Quick start

  1. Link your XRP wallet on the homepage and send XRP to your account's deposit address + destination tag. Your credits update automatically.
  2. On the dashboard, click Create API key. Save the key — it's shown only once.
  3. Make your first request:
curl -X POST https://xrppdf.com/api/v1/ocr \
  -H "X-API-Key: xrpocr_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "[email protected]"

Response (HTTP 200):

{
  "job_id": "aBcD1234xyZ7",
  "pages_charged": 3,
  "status_url": "/api/job/aBcD1234xyZ7/status"
}

2. Authentication

Every /api/v1/* endpoint requires an API key. You can pass it either way:

Authorization: Bearer xrpocr_live_...
# or
X-API-Key: xrpocr_live_...
Never expose your API key in client-side code. Keys have full access to your credits. If a key leaks, revoke it immediately from the dashboard and rotate.

Error responses

CodeMeaning
400Malformed request (bad PDF, invalid params).
401Missing / invalid / revoked API key.
402Insufficient credits. Top up with XRP to continue.
403Not allowed for this auth method (e.g. API key trying to manage keys).
429Rate-limited OR concurrency limit reached.
502Upstream OCR engine unavailable (credits are refunded automatically).

3. Endpoints

POST /api/v1/ocr

Submit a PDF for OCR. Multipart form upload, field name file.

HeaderRequiredPurpose
Authorization / X-API-KeyyesAPI key
Idempotency-KeyrecommendedClient-generated UUID (8–100 chars). Repeat requests with the same key replay the first response byte-for-byte for 24 h — safe to retry on network errors without double-charging.

Success body:

{
  "job_id": "<url-safe 12 chars>",
  "pages_charged": <int>,
  "status_url": "/api/job/<job_id>/status"
}

GET /api/v1/jobs/{job_id}

Poll a job's status. The response shape is stable; new fields may be added.

{
  "job_id": "aBcD1234xyZ7",
  "status": "complete",        // queued | processing | complete | error | deleted
  "total_pages": 3,
  "overall_confidence": 95.4,  // Tesseract mean confidence (0–100)
  "processing_seconds": 7.2,
  "pages_charged": 3,
  "error_message": null,
  "created_at": 1713400000,
  "completed_at": 1713400008,
  "expires_at": 1713486400,    // output auto-deleted after this
  "download_url": "/download/aBcD1234xyZ7"   // null until status=complete
}

GET /download/{job_id}

Returns the searchable PDF. Requires the same API key that submitted the job. Stream the body to disk. After expires_at, returns HTTP 410.

GET /api/v1/account

Shows your current balance, deposit tag, concurrency limit, and in-flight job count. Useful for pre-flight checks before a batch submission.

4. Idempotency

All POST /api/v1/ocr requests should include an Idempotency-Key header. If your HTTP client retries after a network error, we'll return the original response — no duplicate job, no duplicate debit. Cached responses live for 24 hours.

curl -X POST https://xrppdf.com/api/v1/ocr \
  -H "X-API-Key: xrpocr_live_..." \
  -H "Idempotency-Key: batch-2026-04-17-invoice-00042" \
  -F "[email protected]"

5. Webhooks

Register a webhook from the dashboard (or via API) and we'll POST a signed payload to your URL when each job finishes — job.completed or job.failed. Delivery retries with exponential backoff up to 5 attempts (30s, 2m, 10m, 30m, 2h). Respond with any 2xx to acknowledge.

Payload

{
  "event": "job.completed",
  "job_id": "aBcD1234xyZ7",
  "status": "complete",
  "total_pages": 3,
  "overall_confidence": 95.4,
  "processing_seconds": 7.2,
  "pages_charged": 3,
  "expires_at": 1713486400,
  "error_message": null,
  "timestamp": 1713400008
}

Verifying the signature

Each webhook carries two headers:

HeaderValue
X-XRPOCR-TimestampUnix timestamp (seconds)
X-XRPOCR-Signaturesha256=<hex>
X-XRPOCR-Job-IdJob public ID (convenience)

The signature is HMAC_SHA256(secret, "<timestamp>." + raw_body), using the webhook secret shown when you registered. Reject requests whose signature doesn't match OR whose timestamp is more than 5 minutes old to prevent replay attacks.

# Python verification example
import hmac, hashlib

def verify(secret, timestamp, signature, raw_body):
    expected = "sha256=" + hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

6. Concurrency limits

Each account has a concurrency cap — the max number of jobs that can be in flight (queued or processing) at the same time. Exceeding it returns HTTP 429 with {"in_flight": N, "limit": N}. Defaults:

TierConcurrency
Pay-as-you-go2
Pro5
Scale20
EnterpriseCustom

Higher limits are granted automatically after your first qualifying deposit (Pro bundle ≥ 8 XRP, Scale bundle ≥ 30 XRP). Need more? Contact us.

7. Python client example

import uuid, time, requests

API = "https://xrppdf.com"
KEY = "xrpocr_live_..."

def ocr(pdf_path):
    r = requests.post(
        f"{API}/api/v1/ocr",
        headers={"X-API-Key": KEY, "Idempotency-Key": str(uuid.uuid4())},
        files={"file": open(pdf_path, "rb")},
        timeout=60,
    )
    r.raise_for_status()
    return r.json()["job_id"]

def wait(job_id, timeout=900):
    end = time.time() + timeout
    while time.time() < end:
        r = requests.get(f"{API}/api/v1/jobs/{job_id}",
                         headers={"X-API-Key": KEY}, timeout=15)
        j = r.json()
        if j["status"] == "complete":
            return j
        if j["status"] == "error":
            raise RuntimeError(j.get("error_message"))
        time.sleep(2)
    raise TimeoutError(job_id)

def download(job_id, out_path):
    r = requests.get(f"{API}/download/{job_id}",
                     headers={"X-API-Key": KEY}, stream=True, timeout=120)
    r.raise_for_status()
    with open(out_path, "wb") as f:
        for chunk in r.iter_content(65536):
            f.write(chunk)

job = ocr("invoice.pdf")
info = wait(job)
download(job, "invoice_searchable.pdf")
print(f"Done in {info['processing_seconds']}s, confidence {info['overall_confidence']}%")

8. Rate limits & pricing

9. Payments & refunds

All payments are in XRP. Credits are issued only on validated ledger transactions to our deposit address with your account's destination tag. Nothing is credited speculatively; the listener ignores failed, tentative, or issued-currency amounts.

Refund policy. Refunds are issued in XRP on-chain for hard processing failures only — the engine returned an error after the job started, or the output download failed. The refund is sent back to the same wallet that funded your account, at your account's blended effective per-page rate (so bulk buyers get bulk-rate refunds).

  • Where it goes: the wallet_address on file for the account (the one you linked).
  • When it fires: automatically, on detected hard failure — no support ticket required.
  • Delivery: within minutes of failure, from the hot wallet, with exponential-backoff retry on ledger errors.
  • Dust floor: refunds below 0.1 XRP equivalent are credited to your balance instead of sent on-chain, to save you the ledger fee.
  • Safety net: if the on-chain send cannot be completed after 5 attempts, the pages are credited back to your account so you're never stuck.

Soft failures (engine unreachable before your job was accepted, concurrency-limit race) don't consume service and are handled by a straight credit refund — your balance is restored immediately so you can retry.

10. Support

Problems, feature requests, or enterprise questions: [email protected]. Include your account's wallet address (never your API key).

Why do tokens and headers say xrpocr? XRPpdf was originally launched as XRPOCR. API key prefixes (xrpocr_live_) and webhook headers (X-XRPOCR-*) are stable wire-protocol identifiers — they won't change. Treat them as opaque strings. The public brand and domain are XRPpdf / xrppdf.com going forward; xrpocr.com redirects here.