API documentation

v1

One endpoint turns photos of passports, identity cards and driving licences into structured person data. This page is the complete reference: authentication, the request and response contracts, error codes, quotas and ready-to-run examples.

Introduction

The API is served from the same origin as your GoParse deployment. The examples on this page use https://YOUR-DEPLOYMENT as a placeholder — replace it with the origin you signed up on. All endpoints live under /api/v1, accept and return JSON (the parse endpoint accepts multipart form data), and use UTF-8 throughout.

A single call to POST /api/v1/parse accepts up to 8 document photos and returns one entry per person found: name, date of birth, citizenship, document type and number, and more — with ICAO 9303 MRZ check-digit validation and automatic front/back merging.

Parsing is synchronous

A request completes in up to 2 seconds while the documents pass through the recognition pipeline. Keep the HTTP connection open and give your client a timeout of ~30 seconds for headroom. Do not retry while a request is still in flight — you would only queue duplicate work.

Prefer to try it without writing code? The playground runs real parses against your account straight from the browser.

Authentication

Create and revoke API keys under Dashboard → API keys. Keys look like pid_live_… and are shown exactly once at creation; only a SHA-256 hash is stored at rest, so a lost key cannot be recovered — revoke it and create a new one. Pass the key on every request using either header form:

Request headers
Authorization: Bearer pid_live_YOUR_KEY

# — or, equivalently —

X-API-Key: pid_live_YOUR_KEY

Requests without a key are rejected with 401 missing_api_key; unknown or revoked keys with 401 invalid_api_key. Treat keys like passwords: keep them in server-side configuration and never ship them in browser or mobile code.

POST/api/v1/parse

Parses one request worth of document photos and returns structured data for every person found. Send multipart/form-data with the file field images repeated once per file.

Request

Part / headerRequiredDescription
imagesYesMultipart file part, repeated 1–8 times. JPEG, PNG, WebP or HEIC photos or scans, up to 15 MB each.
X-Request-IDNoYour own correlation ID. Echoed back as request_id in the body and in the X-Request-ID response header; auto-generated when omitted.
AuthorizationYesAPI key — see Authentication.

Front and back, multiple people. Send both sides of a card as two separate images, or as one photo showing both sides — sides are detected, paired and merged into a single entry in persons[]. You can mix several documents (for example two family members) in one request; each person becomes one entry.

Example request (curl)
curl -X POST https://YOUR-DEPLOYMENT/api/v1/parse \
  -H "Authorization: Bearer pid_live_YOUR_KEY" \
  -F "images=@passport.jpg" \
  -F "images=@id-card-front.jpg" \
  -F "images=@id-card-back.jpg" \
  --max-time 600

Response

On success the endpoint returns 200 with request_id, persons[], warnings[] and processing. Each entry in persons[] has exactly these nine string fields:

FieldConvention
citizenshipEnglish country name, for example Italy.
place_of_birthMunicipality when the person was born in Italy; otherwise the country of birth.
family_nameSurname exactly as printed on the document — diacritics preserved.
given_nameGiven name(s) exactly as printed — diacritics preserved.
date_of_birthYYYY-MM-DD
sexM or F.
issuing_authorityMunicipality for Italian-style identity cards; otherwise the issuing country.
document_typeOne of passport, identity_card, driving_licence, or empty when the type could not be determined.
document_numberValidated against the ICAO 9303 MRZ check digits whenever a machine-readable zone is available.

Empty means unsure — the API never guesses

Any field may be an empty string. When a value cannot be read with confidence, the pipeline returns "" rather than a plausible-looking guess. Treat empty strings as “not extracted”, not as errors — this is what makes the output safe to feed into KYC and registration flows.

warnings[] contains human-readable notes about anything unusual — for example a missing back side or a failed check digit — without ever including document contents. processing reports images_received, documents_detected and elapsed_s.

Example response (specimen data)
{
  "request_id": "req_9f2b4c7d",
  "persons": [
    {
      "citizenship": "Serbia",
      "place_of_birth": "Bosnia and Herzegovina",
      "family_name": "TANACKOVIĆ",
      "given_name": "RATIMIR",
      "date_of_birth": "1988-12-13",
      "sex": "M",
      "issuing_authority": "Serbia",
      "document_type": "passport",
      "document_number": "350096319"
    },
    {
      "citizenship": "Italy",
      "place_of_birth": "Sant'Angelo Lodigiano",
      "family_name": "ROSSI",
      "given_name": "MARIA",
      "date_of_birth": "1994-03-02",
      "sex": "F",
      "issuing_authority": "Roma",
      "document_type": "identity_card",
      "document_number": "CA00000AA"
    }
  ],
  "warnings": [],
  "processing": {
    "images_received": 3,
    "documents_detected": 3,
    "elapsed_s": 1.8
  }
}

The example uses fictional specimen documents from public research datasets — no real personal data.

Errors

Every error uses the same JSON shape with a stable machine-readable code and a human-readable message:

Error shape
{
  "error": {
    "code": "quota_exceeded",
    "message": "This request would process 3 images but only 1 of the 500 parses in the Starter plan remain this month (one parse = one image). Upgrade your plan at /dashboard/billing, or contact us for pay-as-you-go volume.",
    "request_id": "req_9f2b4c7d"
  }
}
StatusCodeMeaning and what to do
400bad_request · no_images · too_many_imagesThe body was not readable multipart data, the images field was empty, or more than 8 files were sent. Fix the request; do not retry unchanged.
401missing_api_key · invalid_api_keyNo key was sent, or the key is unknown or revoked. Check the header and the key in the dashboard.
402payment_requiredThe last subscription payment failed. Update the payment method under Billing to resume parsing.
413image_too_largeOne image exceeds 15 MB. Resize or re-encode it before uploading.
429quota_exceededNot enough parses remain for the request's image count (one parse = one image) — check the X-Quota-Remaining header. Upgrade under Billing or contact us for pay-as-you-go volume.
502upstream_unreachableThe parsing service is temporarily unreachable. Retry shortly.
503busyThe processing queue is full (see the concurrency note under Quotas). Retry with exponential backoff.
504upstream_timeoutThe request exceeded the processing deadline. Retry; consider sending fewer images per request.

When contacting support about an error, include the request_id — it lets us trace the request without any document contents ever being stored.

Quotas and limits

A parse is one image processed: a successful request (HTTP 200) consumes one parse per image it contains, so a front-and-back upload uses two parses. Monthly quotas reset with your Stripe billing period; the trial quota is a one-off lifetime budget.

PlanPriceIncluded parses
Free TrialFree25 (lifetime)
Starter€15 / month500 / month
Pro€50 / month2,500 / month
EnterpriseCustom (pay as you go)Unlimited
  • Every metered response carries an X-Quota-Remaining header so your integration can monitor headroom without extra calls (omitted on unlimited plans).
  • Fair use: at most 2 concurrent parse requests per account. Queue additional requests client-side; requests beyond the limit receive 503 busy.
  • Per request: up to 8 images, 15 MB each.
  • Compare plans on the pricing page; the Enterprise pay-as-you-go tier is arranged by contacting us.

Code examples

Complete, runnable examples for the three most common stacks. Replace https://YOUR-DEPLOYMENT with your deployment origin and set your API key in the environment.

curl
curl -X POST https://YOUR-DEPLOYMENT/api/v1/parse \
  -H "Authorization: Bearer pid_live_YOUR_KEY" \
  -F "images=@passport.jpg" \
  -F "images=@id-card-front.jpg" \
  -F "images=@id-card-back.jpg" \
  --max-time 600
Node.js (18+, no dependencies)
import { readFile } from "node:fs/promises";

const BASE = "https://YOUR-DEPLOYMENT";
const paths = ["passport.jpg", "id-card-front.jpg", "id-card-back.jpg"];

const form = new FormData();
for (const path of paths) {
  const bytes = await readFile(path);
  form.append("images", new Blob([bytes], { type: "image/jpeg" }), path);
}

const res = await fetch(`${BASE}/api/v1/parse`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PARSEID_API_KEY}` },
  body: form,
  // Parsing is synchronous and completes within ~2 s — 30 s is generous headroom.
  signal: AbortSignal.timeout(30_000),
});

const data = await res.json();
if (!res.ok) {
  throw new Error(`Parse failed: ${data.error.code} — ${data.error.message}`);
}

for (const person of data.persons) {
  console.log(person.document_type, person.family_name, person.given_name);
}
Python (requests)
import os
import requests

BASE = "https://YOUR-DEPLOYMENT"
paths = ["passport.jpg", "id-card-front.jpg", "id-card-back.jpg"]

# Repeat the multipart field "images" once per file.
files = [("images", (p, open(p, "rb"), "image/jpeg")) for p in paths]

res = requests.post(
    f"{BASE}/api/v1/parse",
    headers={"X-API-Key": os.environ["PARSEID_API_KEY"]},
    files=files,
    timeout=600,  # parsing is synchronous: keep the connection open
)

data = res.json()
if res.status_code != 200:
    raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")

for person in data["persons"]:
    print(person["document_type"], person["family_name"], person["given_name"])

Data protection

GoParse is built GDPR-first. In practice this means:

  • Document images and the extracted personal data are processed in memory only and discarded as soon as your response is sent — they are never written to disk, object storage or the database.
  • Usage logs contain aggregate metadata only: document type, image and person counts, timing and status. No names, numbers, photos or any other document contents.
  • You remain the data controller; GoParse processes documents solely on your instructions under our Data Processing Agreement.
  • Full details are in the privacy policy.

GET/api/v1/key

A zero-cost smoke test for your integration: it authenticates exactly like the parse endpoint and returns the key, plan and live quota state without consuming any quota. Ideal for onboarding checks and CI pipelines. quota.included is null on unlimited plans.

Validate a key (curl)
curl https://YOUR-DEPLOYMENT/api/v1/key \
  -H "Authorization: Bearer pid_live_YOUR_KEY"
Example response
{
  "valid": true,
  "key": { "name": "Production backend", "prefix": "pid_live_A1b2", "last4": "k9Qz" },
  "plan": { "id": "starter", "name": "Starter" },
  "quota": { "used": 132, "included": 500, "remaining": 368 }
}