UTF-8 JSON over HTTPS. Identity is controlled by one key per unit, not by an account. Every call is logged in the operator’s API log, with keys masked.
Basehttps://parcid.com/api/v1
Try it
Create your first PID.
Enrolment of class api, level 0 (registered, unverified). Pick the unit profile; dimensions and weight are optional. If you pick photos, your browser computes their SHA-256 fingerprint and only the fingerprints are sent: the files never leave your device.
Specification v0.1 · draft: routes and semantics may change before launch.
API v1
Endpoints
https://parcid.com/api/v1 · JSON UTF-8 · no account to enrol and resolve
Validates format and check, normalises, and says whether it exists and its status. For label readers, forms and agents: cheap, and it does not count as a resolution.
Enrolment: creates the PID and returns the owner key only once, with the sealed record. JSON if you only send fingerprints; multipart if you upload files. An SSCC comes in as a link.
GS1 Digital Link: a publicly linked SSCC resolves to its PIDs. Each carries a claim: “operator” (linked by an identified operator) or “self_declared” (unverified: anyone can link a public SSCC). The PID is not a GS1 key.
Sighting with 1:1 comparison (SPEC §8). Made by an operator: its pidd_ key with the contracted service goes in Authorization and the unit’s owner key in X-Parcid-Owner-Key.
The day’s full export as JSON Lines + gzip: units (by a per-unit salted tag, pid_tag + pid_salt; never the PID in clear), ledger records, anchors and a signed manifest.
This site’s visit counter: path, referrer and language. No cookies or identifiers; the IP is not stored. Returns 204.
GET /verify/{PID}
No authentication
Validates format and check, normalises, and says whether it exists and its status. For label readers, forms and agents: cheap, and it does not count as a resolution.
const r = await fetch('https://parcid.com/api/v1/parcels/PID-1-7QC3-Z79D-G6RZ-8E');
if (r.status === 404) console.log('valid format, not registered');
const unit = await r.json();
// status, unit_profile, assurance_level, lineage, enrolment.anchor…
POST /parcels
No account (optional developer key)
Enrolment: creates the PID and returns the owner key only once, with the sealed record. JSON if you only send fingerprints; multipart if you upload files. An SSCC comes in as a link.
const r = await fetch('https://parcid.com/api/v1/parcels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
capture_class: 'api',
unit_profile: 'pallet_load', // parcel | pallet_load | container_load | tote | bag | crate
captured_at: new Date().toISOString(),
consent: { no_personal_data: true, terms_version: '2026-10' },
}),
});
const { pid, owner_key, enrolment_record } = await r.json();
// 201 · store owner_key and enrolment_record now: the key is shown only once
GET /00/{SSCC}
No authenticationoutside /api/v1
GS1 Digital Link: a publicly linked SSCC resolves to its PIDs. Each carries a claim: “operator” (linked by an identified operator) or “self_declared” (unverified: anyone can link a public SSCC). The PID is not a GS1 key.
curl -X POST -H "Authorization: Bearer $PIDK" https://parcid.com/api/v1/parcels/PID-1-7QC3-Z79D-G6RZ-8E/rotate-key
const r = await fetch('https://parcid.com/api/v1/parcels/PID-1-7QC3-Z79D-G6RZ-8E/rotate-key', {
method: 'POST', headers: { Authorization: 'Bearer ' + ownerKey },
});
const { owner_key } = await r.json(); // the previous key is revoked
POST /parcels/{PID}/sightings
Operator key + owner key
Sighting with 1:1 comparison (SPEC §8). Made by an operator: its pidd_ key with the contracted service goes in Authorization and the unit’s owner key in X-Parcid-Owner-Key.
curl -X POST https://parcid.com/api/v1/parcels/PID-1-7QC3-Z79D-G6RZ-8E/sightings \
-H "Authorization: Bearer $PIDD" -H "X-Parcid-Owner-Key: $PIDK" -F capture_class=station \
-F 'evidence[top]=@top.jpg' -F 'evidence[perspective]=@perspective.jpg'
# $PIDD: operator key with the contracted service · without it: 402 {"error":"paid_service"}
const form = new FormData();
form.append('capture_class', 'mobile_sdk');
form.append('evidence[top]', file);
const r = await fetch('https://parcid.com/api/v1/parcels/PID-1-7QC3-Z79D-G6RZ-8E/sightings', {
method: 'POST',
headers: { Authorization: 'Bearer ' + operatorKey, 'X-Parcid-Owner-Key': ownerKey },
body: form,
});
// match.decision: 'match' | 'review' | 'no_match', with an explanation
POST /reservations
Certified device or developer key
Reserves blocks of PIDs for offline stations, signed by the device. They expire after 90 days and are never reissued.
const { exports } = await (await fetch('https://parcid.com/api/v1/export')).json();
const latest = exports.at(-1); // daily, CC0, units identified by a per-unit salted tag (pid_tag + pid_salt)
GET /export/parcid-export-{AAAAMMDD}.jsonl.gz
No authentication
The day’s full export as JSON Lines + gzip: units (by a per-unit salted tag, pid_tag + pid_salt; never the PID in clear), ledger records, anchors and a signed manifest.
const gz = await fetch(latest.url);
const text = await new Response(gz.body.pipeThrough(new DecompressionStream('gzip'))).text();
const res = await verifyExport(text.split('\n')); // from parcid-verify.js
GET /.well-known/parcid
No authenticationoutside /api/v1
Registry metadata: version, public keys (kid), ID format, transparency, export and limits.
This site’s visit counter: path, referrer and language. No cookies or identifiers; the IP is not stored. Returns 204.
curl -X POST https://parcid.com/api/v1/beacon -H 'Content-Type: application/json' \
-d '{ "p": "/como-funciona/", "r": "", "l": "es" }'
# 204 · no cookies, no identifiers, IP never stored
navigator.sendBeacon('/api/v1/beacon', new Blob([JSON.stringify({
p: location.pathname, r: document.referrer, l: 'es',
})], { type: 'application/json' })); // only the path, referrer host and language are counted
Enrolments with a free developer key (verified email)
1,000 per month
Public resolution
60 per minute per IP; 600 per minute with a key
GET /verify
600 per minute per IP
Raw evidence on the free tier
30 days encrypted; then fingerprints only
Physical signature and record
Kept indefinitely
Size per enrolment
25 MB per image, 200 MB video, 50 MB point cloud; 300 MB in total
Enrolments without a device signature stay at level 0 and count against stricter limits.
Each source (device key, developer key, IP) builds a reputation: many orphan or inconsistent enrolments lower the maximum level it can obtain. Published in aggregate, not per source.
Downloads
Public-domain reference code.
Generate, normalise and validate a PID, and verify records, proofs and exports, with no dependencies. Tested: 0 substitutions and 0 adjacent transpositions undetected.