API reference / Quickstart
Quickstart
Upload a document, wait for it to be processed and read typed values with their evidence — then receive results by webhook instead of polling. You need a workspace with at least one enabled document type.
1. Create an API key
In your workspace open Integrations → API keys, create a key with the documents:read and documents:write scopes, and copy it — it is shown once.
export DOCWISE_API_KEY="dwk_…"2. Upload a document
Send the file as multipart form data. The response returns immediately with the document id and status QUEUED. Reusing the same Idempotency-Key on a retry returns the same document instead of a duplicate.
curl -sS -X POST "https://www.docwise.io/api/v1/documents/" \
-H "Authorization: Bearer $DOCWISE_API_KEY" \
-H "Idempotency-Key: invoice-2026-0042" \
-F "file=@invoice.pdf"
# {"id":"cm…","object":"document","status":"QUEUED","idempotent_replay":false}3. Poll for the status
Processing usually takes seconds. Poll every few seconds until the status is COMPLETED or NEEDS_REVIEW (a person must confirm some fields), or FAILED. Better: subscribe to webhooks (below).
DOC=cm… # the id from step 2
until status=$(curl -sS "https://www.docwise.io/api/v1/documents/$DOC/" \
-H "Authorization: Bearer $DOCWISE_API_KEY" | jq -r .status) \
&& [[ "$status" =~ ^(COMPLETED|NEEDS_REVIEW|FAILED|REJECTED|BLOCKED|CANCELLED)$ ]]; do
sleep 3
done; echo "$status"4. Read the result
result.data holds the typed values; result.fields lists every value with its JSON pointer, confidence and evidence (page, quoted text and boxes).
curl -sS "https://www.docwise.io/api/v1/documents/$DOC/" -H "Authorization: Bearer $DOCWISE_API_KEY" \
| jq '{type: .result.document_type.name, data: .result.data,
low_confidence: [.result.fields[] | select(.confidence != null and .confidence < 0.8) | .path]}'5. Export many results
Export all completed documents of one type as CSV, JSON or Excel. Tables such as line items become one row each.
curl -sS "https://www.docwise.io/api/v1/documents/export/?type=TYPE_ID&format=csv" \
-H "Authorization: Bearer $DOCWISE_API_KEY" -o invoices.csv
# Find TYPE_ID with: curl -sS "https://www.docwise.io/api/v1/document-types/" -H "Authorization: Bearer $DOCWISE_API_KEY"Large files: presigned uploads
For big files or browser uploads, send the bytes straight to storage. This needs a key with the uploads:write scope.
# Large files: declare, PUT the bytes to storage, then complete.
SHA=$(shasum -a 256 big.pdf | cut -d' ' -f1); SIZE=$(wc -c < big.pdf | tr -d ' ')
START=$(curl -sS -X POST "https://www.docwise.io/api/v1/uploads/" \
-H "Authorization: Bearer $DOCWISE_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: big-2026-0042" \
-d "{\"fileName\":\"big.pdf\",\"mimeType\":\"application/pdf\",\"size\":$SIZE,\"sha256\":\"$SHA\"}")
UPLOAD=$(echo "$START" | jq -r .session.id)
curl -sS -X PUT "$(echo "$START" | jq -r .upload.url)" \
$(echo "$START" | jq -r '.upload.headers | to_entries[] | "-H \(.key):\(.value)"') \
--data-binary @big.pdf
curl -sS -X POST "https://www.docwise.io/api/v1/uploads/$UPLOAD/complete/" \
-H "Authorization: Bearer $DOCWISE_API_KEY" -H "Content-Type: application/json" -d '{}'
# Poll https://www.docwise.io/api/v1/uploads/$UPLOAD/ until state is SUCCEEDED; documentId is the new document.Receive webhooks
Register an HTTPS endpoint under Integrations → Webhooks and pick events such as document.completed, document.needs_review, document.failed and review.completed. Each delivery is signed: X-Webhook-Signature: v1=HMAC_SHA256(secret, id + "." + timestamp + "." + rawBody). Verify it on the raw body, reject old timestamps, and deduplicate on X-Webhook-Id because delivery is at least once. Respond with any 2xx within 10 seconds; failures are retried with backoff and can be replayed from the delivery log.
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
const seen = new Set(); // use your database in production
// Verify against the raw bytes, before any JSON parsing.
app.post("/webhooks/docwise", express.raw({ type: "application/json" }), (req, res) => {
const id = req.header("X-Webhook-Id");
const timestamp = req.header("X-Webhook-Timestamp");
const signature = req.header("X-Webhook-Signature") ?? "";
const expected = "v1=" + crypto
.createHmac("sha256", process.env.DOCWISE_WEBHOOK_SECRET)
.update(`${id}.${timestamp}.${req.body}`)
.digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!valid || !fresh) return res.status(400).end();
if (seen.has(id)) return res.status(200).end(); // at-least-once delivery
seen.add(id);
const event = JSON.parse(req.body);
if (event.event === "document.completed") {
console.log(event.data.documentId, event.data.result);
}
res.status(200).end();
});
app.listen(3000);Python (Flask)
import hashlib, hmac, json, os, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["DOCWISE_WEBHOOK_SECRET"].encode()
seen = set() # use your database in production
@app.post("/webhooks/docwise")
def docwise_webhook():
event_id = request.headers.get("X-Webhook-Id", "")
timestamp = request.headers.get("X-Webhook-Timestamp", "0")
signature = request.headers.get("X-Webhook-Signature", "")
raw = request.get_data() # raw bytes, before JSON parsing
message = f"{event_id}.{timestamp}.".encode() + raw
expected = "v1=" + hmac.new(SECRET, message, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(400)
if abs(time.time() - int(timestamp)) > 300:
abort(400)
if event_id in seen: # at-least-once delivery
return "", 200
seen.add(event_id)
event = json.loads(raw)
if event["event"] == "document.completed":
print(event["data"]["documentId"], event["data"]["result"])
return "", 200Next steps
- List documents with cursor pagination and filters.
- Errors and rate limits.
- Download the OpenAPI 3.1 spec to generate a client.