Skip to content

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 "", 200

Next steps