API documentation
v1One 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
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:
Authorization: Bearer pid_live_YOUR_KEY
# — or, equivalently —
X-API-Key: pid_live_YOUR_KEYRequests 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 / header | Required | Description |
|---|---|---|
images | Yes | Multipart file part, repeated 1–8 times. JPEG, PNG, WebP or HEIC photos or scans, up to 15 MB each. |
X-Request-ID | No | Your own correlation ID. Echoed back as request_id in the body and in the X-Request-ID response header; auto-generated when omitted. |
Authorization | Yes | API 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.
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 600Response
On success the endpoint returns 200 with request_id, persons[], warnings[] and processing. Each entry in persons[] has exactly these nine string fields:
| Field | Convention |
|---|---|
citizenship | English country name, for example Italy. |
place_of_birth | Municipality when the person was born in Italy; otherwise the country of birth. |
family_name | Surname exactly as printed on the document — diacritics preserved. |
given_name | Given name(s) exactly as printed — diacritics preserved. |
date_of_birth | YYYY-MM-DD |
sex | M or F. |
issuing_authority | Municipality for Italian-style identity cards; otherwise the issuing country. |
document_type | One of passport, identity_card, driving_licence, or empty when the type could not be determined. |
document_number | Validated against the ICAO 9303 MRZ check digits whenever a machine-readable zone is available. |
Empty means unsure — the API never guesses
"" 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.
{
"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": {
"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"
}
}| Status | Code | Meaning and what to do |
|---|---|---|
| 400 | bad_request · no_images · too_many_images | The 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. |
| 401 | missing_api_key · invalid_api_key | No key was sent, or the key is unknown or revoked. Check the header and the key in the dashboard. |
| 402 | payment_required | The last subscription payment failed. Update the payment method under Billing to resume parsing. |
| 413 | image_too_large | One image exceeds 15 MB. Resize or re-encode it before uploading. |
| 429 | quota_exceeded | Not 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. |
| 502 | upstream_unreachable | The parsing service is temporarily unreachable. Retry shortly. |
| 503 | busy | The processing queue is full (see the concurrency note under Quotas). Retry with exponential backoff. |
| 504 | upstream_timeout | The 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.
| Plan | Price | Included parses |
|---|---|---|
| Free Trial | Free | 25 (lifetime) |
| Starter | €15 / month | 500 / month |
| Pro | €50 / month | 2,500 / month |
| Enterprise | Custom (pay as you go) | Unlimited |
- Every metered response carries an
X-Quota-Remainingheader 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 -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 600import { 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);
}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.
curl https://YOUR-DEPLOYMENT/api/v1/key \
-H "Authorization: Bearer pid_live_YOUR_KEY"{
"valid": true,
"key": { "name": "Production backend", "prefix": "pid_live_A1b2", "last4": "k9Qz" },
"plan": { "id": "starter", "name": "Starter" },
"quota": { "used": 132, "included": 500, "remaining": 368 }
}