CrunchFile API

Ask an employee, driver, contractor or client for a document from your own system — a POD, a timesheet, a signed work order, a receipt. CrunchFile texts them a link; they photograph the paperwork; the straightened, cleaned PDF comes back to you by webhook and a download link. No app or account on their side.

Get a key

Sign in, open Dashboard → Document Requests → Settings & API, and create an API key. Keys look like cf_live_… and are shown once. Send one on every call:

Authorization: Bearer cf_live_your_key

Base URL: https://getcrunchfile.com/api/v1. Requests and responses are JSON. Limit: 120 calls per minute per account.

Create a request

POST /api/v1/requests
{
  "recipient_name":  "Marcus Lee",
  "recipient_phone": "+12145550101",
  "document_type":   "POD",            // any short name: POD, Timesheet, Work order, Receipt, Signed contract, ID…
  "reference":       "Load 48213",     // shown to them and on the file name (job #, invoice #, client)
  "message":         "Signed by the receiver, every page",   // optional
  "external_id":     "load-48213",     // your own id, echoed back
  "send_sms":        true              // text the link now (needs texting set up on your account)
}

Response 201:

{
  "request": {
    "id": "2f6c…",
    "url": "https://getcrunchfile.com/r/Qk3v…",   // the link the driver opens
    "status": "open",                              // open → received (or cancelled)
    "document_type": "POD",
    "reference": "Load 48213",
    "external_id": "load-48213",
    "sms_status": "sent",
    "files": [],
    "created_at": "2026-10-09T21:14:03Z",
    "expires_at": "2026-11-08T21:14:03Z"
  }
}

If you send texts or emails from your own system, leave out send_sms and put url in your message. A request stays open for 30 days and can take up to 10 uploads.

Other endpoints

CallWhat it does
GET /api/v1/requests?status=received&limit=50List requests, newest first.
GET /api/v1/requests/:idOne request with its files.
POST /api/v1/requests/:id/textText (or re-text) the link to them.
POST /api/v1/requests/:id/cancelClose the link.
GET /api/v1/requests/:id/files/:fileIdA download link for one upload (15 minutes). Add ?redirect=1 to be sent straight to the file.

Webhook: know the moment someone uploads

Set a webhook URL in Settings & API. When someone uploads, CrunchFile POSTs to it (retrying twice over a minute on failure):

POST https://your-system.example.com/crunchfile
X-CrunchFile-Event: request.received
X-CrunchFile-Signature: t=1760044443,v1=5f1a…

{
  "event": "request.received",          // request.received on the first upload, request.updated after that, test from the dashboard
  "created_at": "2026-10-09T21:20:43Z",
  "request": {
    "id": "2f6c…", "status": "received", "external_id": "load-48213", "reference": "Load 48213",
    "files": [
      { "id": "9a1e…", "file_name": "POD - Load 48213 - Marcus Lee 2026-10-09.pdf", "file_size": 184233, "page_count": 2,
        "download_url": "https://…"   // valid for 7 days
      }
    ]
  }
}

Verify the signature: take t from the header, compute HMAC-SHA256 of {t}.{raw body} with your signing secret, and compare it to v1. Reject anything older than a few minutes.

// Node
const crypto = require("crypto");
const [t, v1] = req.headers["x-crunchfile-signature"].split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${rawBody}`).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));

Errors

Errors are { "error": { "code": "…", "message": "…" } } with the matching HTTP status: 401 invalid_api_key, 400 invalid_request, 404 not_found, 429 rate_limited, 501 not_configured (texting not set up).

Questions

Email [email protected]. If you're building for a fleet, a field-service team, a clinic or an accounting firm and need something the API doesn't do yet, say so — this is early and shaped by what people need.