Record submitted photo evidence taken with the PixelTrace camera, and flag copies of those photos in your own shop.
Live PixelTrace groups only take photos from the seller's phone camera, in iPhone Safari or Android Chrome, so arrange a person with a phone first. Opening the link on a computer does not use it up. Test keys can upload and confirm with test-photos and test-confirm.
PixelTrace compares submitted photos against photos taken with the PixelTrace camera and published. Photos uploaded from a phone gallery or an existing catalogue cannot be published to a live group, so they never become one of your published photos. Photos a test key uploads stay in test groups and only match other test groups. That is by design: if businesses could register their own photos, anyone could claim stock photos as theirs. Sending such a photo to the check address can still show that it is a copy of a published camera photo.
Browser-only capture is submitted evidence. Its trust level is at most normal, never strong. Public badges say PixelTrace submitted evidence; demo badges say PixelTrace demo evidence. The signature covers the ordered photos, file fingerprints and trust levels, not proof of camera hardware.
PixelTrace lets a seller take a listing's photos on their own phone through a PCIFIC camera page, checks each photo for signs it was taken live, and finds photos that were used in this business before.
| Use case | How PixelTrace helps |
|---|---|
| Shops and classifieds | The seller takes the listing photos on their phone, so buyers see photos taken now. Your server also checks photos against your earlier listings, so a stolen or reused photo goes to a moderator. |
| Resale and second hand (phones, watches, clothes, handbags) | Condition photos taken now, with a certificate code buyers can open. A photo that already sits on another of your listings is found. |
| Car dealers | Walk-around photos taken at the lot, on the day the advert goes live, with a certificate on the advert. An old advert's photo reused on a new one is found. |
| Insurance claims | The claimant takes photos of the damaged item now, on their own phone, and you keep a signed record of the group. A photo that was already used in an earlier claim of yours is found. |
| Rentals and holiday lets | Check-in and check-out condition photos, each group with its own code and time. Hosts can also take their listing photos this way. |
| Stopping stolen photos | Send any photo to the check address before a listing goes live. If it matches one you published, a person looks at both side by side. |
Set these on the dashboard, or with a test key that has the settings permission. The Shop configuration page has the full tables.
A group's status moves capturing, ready, completed and, if you take it back, retracted. A capturing group can also move straight to retracted when cancelled. A group that is never confirmed, or confirmed and never published, is deleted when its time runs out.
| Status | What it means | What happens next |
|---|---|---|
capturing | The camera link is out. The seller is taking photos, or has not opened the link yet. | If nobody confirms before expiresAt, the group is deleted. |
ready | The seller confirmed. The record of the photos is signed. | Publish it within a day, or it is deleted. |
completed | You published it. Photos were compared and the certificate is public. | Stays until you retract it. |
retracted | The seller cancelled it or you took it back. Fingerprints and photo files are deleted. | The certificate stays and says it was retracted. |
Every call needs your key in the Authorization header. These examples use a test key kept in $PCIFIC_TEST_KEY.
curl -X POST https://business.pcific.ai/api/v1/pixel-trace/captures \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"listingId":"listing-123","listingTitle":"Blue denim jacket"}'{
"id": "7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11",
"status": "capturing",
"code": "PCF-IMG-K7M2QX-R4TN9B",
"cameraUrl": "https://business.pcific.ai/pixel-trace/capture?c=7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11#t=<the ticket>",
"expiresAt": "2026-10-20T13:55:02.118203Z",
"captureMinutes": 30,
"minPhotos": 1,
"maxPhotos": 12
}Send the seller cameraUrl. Keep id: you need it to read, publish and retract the group. The code is already final, so you can store it now. expiresAt is when the link stops working if nobody opens it. Opening it starts the seller's own captureMinutes.
curl -X GET https://business.pcific.ai/api/v1/pixel-trace/captures/7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11 \
-H "Authorization: Bearer $PCIFIC_TEST_KEY"{
"id": "7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11",
"status": "ready",
"code": "PCF-IMG-K7M2QX-R4TN9B",
"listingId": "listing-123",
"listingTitle": "Blue denim jacket",
"test": true,
"demo": false,
"createdAt": "2026-10-19T13:55:02.118203Z",
"expiresAt": "2026-10-20T14:02:11.482913Z",
"sealedAt": "2026-10-19T14:02:11.482913Z",
"publishedAt": null,
"certificateUrl": null,
"badgeUrl": null,
"camera": {
"opened": true,
"openedAt": "2026-10-19T13:58:40.907112Z",
"device": {
"userAgent": "Mozilla/5.0 (iPhone; ...)",
"touchPoints": 5,
"coarsePointer": true
},
"refusedOpens": 0
},
"trust": {
"strong": 0,
"normal": 1,
"weak": 0
},
"photos": [
{
"id": "0b6f3a52-91c8-4d0e-a3f4-6c21d8e7b9a0",
"position": 0,
"width": 1440,
"height": 1080,
"bytes": 412331,
"sha256": "9f2b6c1e0d4a7385b1c9e2f06a4d83715bc0e9d2a64f1837c5b09e2d4a6f1c83",
"trust": "normal",
"checks": [
{
"check": "real_tap",
"outcome": "pass",
"detail": "The shutter was pressed by a finger on the screen (contact 14 by 14)."
},
{
"check": "phone",
"outcome": "pass",
"detail": "The camera page ran in a phone browser with a touch screen."
}
],
"matches": null,
"files": {
"preview": "https://files.example/preview?signature=...",
"photo": "https://files.example/photo.jpg?signature=..."
}
}
],
"manifest": "{\"version\":1,\"test\":true,\"code\":\"PCF-IMG-K7M2QX-R4TN9B\",\"group\":\"7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11\",\"listingId\":\"listing-123\",\"sealedAt\":\"2026-10-19T14:02:11.482913+00:00\",\"photos\":[{\"id\":\"0b6f3a52-91c8-4d0e-a3f4-6c21d8e7b9a0\",\"position\":0,\"sha256\":\"9f2b6c1e0d4a7385b1c9e2f06a4d83715bc0e9d2a64f1837c5b09e2d4a6f1c83\",\"trust\":\"normal\"}]}",
"signature": "Qm9n...base64url...",
"keyId": "test"
}The example is shortened: a real photo lists all eight checks, and the file links are long. While status is capturing, the photos listed so far are the ones saved. Do not publish before status is ready. photos[].files.photo is a link to the stored JPEG that works for 10 minutes, so you can download it and compare its hash with sha256.
curl -X POST https://business.pcific.ai/api/v1/pixel-trace/captures/7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11/complete \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{"marketplaceName": "Bramble", "listingUrl": "https://bramble.example/listing-123"}'{
"id": "7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11",
"status": "completed",
"code": "PCF-IMG-K7M2QX-R4TN9B",
"listingId": "listing-123",
"listingTitle": "Blue denim jacket",
"test": true,
"demo": false,
"createdAt": "2026-10-19T13:55:02.118203Z",
"expiresAt": null,
"sealedAt": "2026-10-19T14:02:11.482913Z",
"publishedAt": "2026-10-19T14:20:45.330118Z",
"certificateUrl": "https://business.pcific.ai/pixel-trace/certificates/PCF-IMG-K7M2QX-R4TN9B",
"badgeUrl": "https://business.pcific.ai/api/v1/pixel-trace/certificates/PCF-IMG-K7M2QX-R4TN9B/badge.svg",
"camera": {
"opened": true,
"openedAt": "2026-10-19T13:58:40.907112Z",
"device": {
"userAgent": "Mozilla/5.0 (iPhone; ...)",
"touchPoints": 5,
"coarsePointer": true
},
"refusedOpens": 0
},
"trust": {
"strong": 0,
"normal": 1,
"weak": 0
},
"photos": [
{
"id": "0b6f3a52-91c8-4d0e-a3f4-6c21d8e7b9a0",
"position": 0,
"width": 1440,
"height": 1080,
"bytes": 412331,
"sha256": "9f2b6c1e0d4a7385b1c9e2f06a4d83715bc0e9d2a64f1837c5b09e2d4a6f1c83",
"trust": "normal",
"checks": [
{
"check": "real_tap",
"outcome": "pass",
"detail": "The shutter was pressed by a finger on the screen (contact 14 by 14)."
},
{
"check": "phone",
"outcome": "pass",
"detail": "The camera page ran in a phone browser with a touch screen."
}
],
"matches": {
"level": "same_photo",
"message": "This is the same photo as one published before.",
"found": [
{
"level": "same_photo",
"distance": 0,
"code": "PCF-IMG-H3RWD8-N6VKA2",
"photoId": "c41e7d90-5a2b-4e86-b0f3-8d19a6c2e754",
"marketplaceName": "Bramble",
"listingUrl": "https://bramble.example/listing-88",
"preview": "https://files.example/preview?signature=..."
}
]
},
"files": {
"preview": "https://files.example/preview?signature=...",
"photo": "https://files.example/photo.jpg?signature=..."
}
}
],
"manifest": "{\"version\":1,\"test\":true,\"code\":\"PCF-IMG-K7M2QX-R4TN9B\",\"group\":\"7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11\",\"listingId\":\"listing-123\",\"sealedAt\":\"2026-10-19T14:02:11.482913+00:00\",\"photos\":[{\"id\":\"0b6f3a52-91c8-4d0e-a3f4-6c21d8e7b9a0\",\"position\":0,\"sha256\":\"9f2b6c1e0d4a7385b1c9e2f06a4d83715bc0e9d2a64f1837c5b09e2d4a6f1c83\",\"trust\":\"normal\"}]}",
"signature": "Qm9n...base64url...",
"keyId": "test"
}Here the photo matched one from an earlier listing. photos[].matches.level is the strongest match for that photo, and found lists up to 10 of them. Send the result to a person who looks at both photos. Publishing the same group again returns the same answer and finishes any photo that was not compared yet, so it is safe to retry. Send an Idempotency-Key header too.
<a href="https://business.pcific.ai/pixel-trace/certificates/PCF-IMG-K7M2QX-R4TN9B"><img src="https://business.pcific.ai/api/v1/pixel-trace/certificates/PCF-IMG-K7M2QX-R4TN9B/badge.svg" alt="PixelTrace submitted evidence"></a>curl https://business.pcific.ai/api/v1/pixel-trace/certificates/PCF-IMG-K7M2QX-R4TN9BThe certificate shows the listing, each photo's preview, trust level and checks, and the signed manifest. A match level appears on it only if the level is in your publicLevels setting (none are by default). A group made with a test key has a certificate too, and the page and the badge say it is a test. After a retraction, a previously published certificate says the group was retracted and its badge shows a cross. An unpublished group never gets a public certificate or badge.
curl -X POST https://business.pcific.ai/api/v1/pixel-trace/captures/7d1c5e0a-3b64-4f43-9d71-2f5a9c0e8b11/retract \
-H "Authorization: Bearer $PCIFIC_TEST_KEY"Cancel a capturing group or take back a ready or published group with this call: its fingerprints leave the search, its photo files are deleted and status becomes retracted. Calling it again is safe. Its camera link immediately closes and refuses further photos with a cancellation message. Before confirming, the seller can also choose Cancel photo group below the camera page's photo controls. Starting a credit-billed live group is charged. Cancel or let it expire before any photo is accepted and processed by the server (checked, fingerprinted and stored), and the full creation charge is refunded. Once any photo was processed, or the group was confirmed, published or matched, there is no refund, even if all photos were deleted or the seller switched phones. The cancel response includes refunded (true/false) and refundReason in plain words. Test keys and unlimited accounts change no credits. Automatic refunds apply only to groups created after this fix; older live groups need PCIFIC support: contact support@pcific.co.uk with the group id for a refund review. Refunds restore the balance shown on Usage and GET /api/v1/usage, but original requests and gross spend still count toward rate limits, usage totals and spending limits.
curl -X POST https://business.pcific.ai/api/v1/pixel-trace/check \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg{
"level": "same_photo",
"message": "This is the same photo as one published before.",
"matches": [
{
"level": "same_photo",
"distance": 0,
"code": "PCF-IMG-H3RWD8-N6VKA2",
"photoId": "c41e7d90-5a2b-4e86-b0f3-8d19a6c2e754",
"marketplaceName": "Bramble",
"listingUrl": "https://bramble.example/listing-88",
"preview": "https://files.example/preview?signature=..."
}
],
"searched": "this shop only, same key kind (test or live)"
}{
"level": "no_match",
"message": "No published photo matches this one.",
"matches": [],
"searched": "this shop only, same key kind (test or live)"
}Set the Content-Type to image/jpeg, image/png, image/webp. curl's --data-binary alone sends a form content type, which makes the bytes unreadable on the way, so always add the header. With the Node fetch function, pass the file's bytes (a Buffer) as the body.
Test and live certificates use independent signing keys. Test key IDs are test and test-YYYY-MM-DD; production IDs are pixel-trace-1 and pixel-trace-YYYY-MM-DD. Both are published at the same JWKS address, with no shared key material. New signed manifests include test: true or test: false. For production, use the example with --production: it requires a production key and the signed test: false flag. For a test integration, use --test. Changing the outer test or keyId fields cannot change the signed environment.
Older certificates, including test certificates signed with a production key before this fix, keep their original signed text and signature. Their key stays published while the group exists, unless it leaks. Run the example without an environment option to check that legacy signature. It reports that there is no signed environment, so it confirms only who signed the text. Older records have no signed test flag and must be rejected for production use, even if the outer test field says false. Fetch the public certificate to see its test label, or create a new capture with the intended API key for a record with a signed environment. Existing test groups still expire 24 hours after they start.
When the seller confirms, PCIFIC signs the manifest with its private key (Ed25519 is the kind of signature). The group and the certificate both return three things: manifest (the exact signed text), signature (base64url, a URL-safe form of base64) and keyId. PCIFIC publishes the matching public key at https://business.pcific.ai/.well-known/jwks.json, as a JWKS (a list of public keys). Each key's x value is the key and its kid is its name, which a record carries as keyId: pixel-trace-1 for the first key, then pixel-trace- and the day a later key was added, like pixel-trace-2026-10-22. The newest key in the matching environment signs new records, and an older key stays in the list while records it signed are kept, so always pick the key by kid, never the first in the list. If a key ever leaks, PCIFIC stops publishing it and signs its records again with the newest key for each group's environment: read the group or certificate again for its new signature and keyId.
// verify-pixeltrace.mjs. Run: node verify-pixeltrace.mjs group.json [--production|--test]
import { createPublicKey, verify } from "node:crypto";
import { readFile } from "node:fs/promises";
const group = JSON.parse(await readFile(process.argv[2], "utf8"));
const { keys } = await (await fetch("https://business.pcific.ai/.well-known/jwks.json")).json();
const jwk = keys.find((key) => key.kid === group.keyId);
if (!jwk) throw new Error("PCIFIC publishes no key called " + group.keyId + ". Fetch the certificate again for its current keyId and signature.");
const publicKey = createPublicKey({ key: jwk, format: "jwk" });
const signature = Buffer.from(group.signature, "base64url");
// The manifest is text. Check it exactly as it came, never parse it and write it again.
const signed = verify(null, Buffer.from(group.manifest, "utf8"), publicKey, signature);
const manifest = JSON.parse(group.manifest);
const testKey = jwk.kid === "test" || jwk.kid.startsWith("test-");
const mode = process.argv[3];
if (mode && !["--production", "--test"].includes(mode)) throw new Error("Unknown verification mode. Use --production for live records or --test for test records.");
const environmentMatches = mode === "--production" ? manifest.test === false && !testKey : mode === "--test" ? manifest.test === true && testKey : true;
const valid = signed && environmentMatches;
console.log(valid ? "Valid: PCIFIC signed exactly this manifest." : "NOT valid: do not trust this record. Fetch the certificate again and check its signature with the current published key.");
if (signed && !environmentMatches) console.log("Wrong signing environment for " + mode + ": the signing key and signed test flag on this certificate do not match this mode. Fetch a new certificate made with the intended API key.");
if (valid) console.log(manifest.test === true ? "Test certificate, never use as production evidence." : manifest.test === false && !testKey ? "Production certificate." : "Legacy signature only: no signed environment. Reject for production use.");
process.exitCode = valid ? 0 : 1;curl https://business.pcific.ai/api/v1/pixel-trace/certificates/PCF-IMG-K7M2QX-R4TN9B > group.json
node verify-pixeltrace.mjs group.json| Field | Type | Required | What it is |
|---|---|---|---|
listingId | string | Yes | Your business's own listing id as text, not necessarily a UUID. After spaces at the ends are trimmed it must contain 1 to 120 characters. |
listingTitle | string | No | A title shown to the seller on the camera page and on the certificate. Longer than 120 characters is cut. |
| Field | Type | Required | What it is |
|---|---|---|---|
marketplaceName | string | No | Your shop's name, shown on the certificate and next to this group in later matches. Longer than 120 characters is cut. |
listingUrl | string | No | The listing page, a full https:// address of up to 2,000 characters (a test key may use http://, including localhost). PCIFIC never opens this address: it only stores it and shows it as a link on the certificate and next to this group in later matches. Ask PCIFIC document and item lookup addresses must be public because PCIFIC fetches them. The whole body may be left out. |
| Field | Type | Required | What it is |
|---|---|---|---|
(the request body) | object | Yes | The photo's raw bytes, not JSON and not base64. JPEG, PNG or WebP, up to 15 MB, at least 640 pixels on the longest side. |
Reading a group (GET /api/v1/pixel-trace/captures/{id}) and retracting it (POST .../retract) take no body. Settings are changed with GET and PUT /api/v1/pixel-trace/settings, which needs a test key with the settings permission or the dashboard: a live key can read settings but never change them.
| Field | Type | Required | What it is |
|---|---|---|---|
id | string | Yes | The group's id. Use it to read, publish and retract the group. |
status | string | Yes | Always capturing here. It then moves to ready, completed and, if taken back, retracted. |
code | string | Yes | The group's public code, your code letters then IMG then two groups of six characters, for example PCF-IMG-K7M2QX-R4TN9B. |
cameraUrl | string | Yes | PCIFIC's camera page for this group. Send it to the seller. It opens on one phone only. |
expiresAt | string | Yes | When the link stops working if nobody opens it (an ISO 8601 date and time). Opening it moves this to captureMinutes later. |
captureMinutes | integer | Yes | How long the seller has once they open the link. |
minPhotos | integer | Yes | Photos the seller must take before they can confirm. |
maxPhotos | integer | Yes | The most photos a group can hold, always 12. |
| Field | Type | Required | What it is |
|---|---|---|---|
id | string | Yes | The group's id. |
status | string | Yes | capturing, ready, completed or retracted. |
code | string | Yes | The public code, for example PCF-IMG-K7M2QX-R4TN9B. |
listingId, listingTitle | string | Yes | What you sent when you started the group. listingTitle is null if you sent none. |
test | boolean | Yes | true when a test key made the group. Its certificate says it is a test. |
demo | boolean | Yes | false for your groups. true only for PCIFIC's own demo. |
createdAt, expiresAt | string | Yes | ISO 8601 dates and times. expiresAt is when the group is deleted if it is not finished: the link's end while capturing, a day after confirming while ready. It is null once published. |
sealedAt, publishedAt | string | Yes | When the seller confirmed and when you published. null until then. |
certificateUrl, badgeUrl | string | Yes | The public certificate page and the badge image. null until the group is completed. |
camera | object | Yes | opened (true once a phone opened the link), openedAt, device (what the browser said about the phone) and refusedOpens (how many other devices tried to open the link after the first and were refused). |
trust | object | Yes | How many photos are strong, normal and weak. |
photos | object[] | Yes | In the seller's order. Each has id, position, width, height, bytes, sha256 (of the stored file), trust, checks, matches and files. |
photos[].checks | object[] | Yes | The eight checks. Each has check (its name), outcome (pass, fail or unknown) and detail (a sentence saying what was found). |
photos[].matches | object | Yes | null until you publish. Then level (the strongest match), message, and found (up to 10 matches). |
photos[].files | object | Yes | preview and photo: links that work for 10 minutes. Both are null once the files are deleted. |
manifest, signature, keyId | string | Yes | The signed record. null until the seller confirms. manifest is the exact text that was signed. |
| Field | Type | Required | What it is |
|---|---|---|---|
level | string | Yes | same_photo, likely_same or possibly_related. |
distance | integer | Yes | How many fingerprint bits differ. Smaller means closer. 0 is the same file. |
code | string | Yes | The code of the earlier group that holds this photo. |
photoId | string | Yes | The earlier photo's id. |
marketplaceName, listingUrl | string | Yes | What you sent when you published the earlier group. null if you sent none. |
preview | string | No | A link (10 minutes) to the earlier photo's preview, so a person can look side by side. Left out if its files were deleted. |
| Field | Type | Required | What it is |
|---|---|---|---|
level | string | Yes | The strongest match found: same_photo, likely_same, possibly_related or no_match. |
message | string | Yes | A sentence you can show a reviewer. |
matches | object[] | Yes | Up to 10 matches, strongest and closest first. Empty for no_match. Each has the fields in the match table. |
searched | string | Yes | Always "this shop only, same key kind (test or live)". |
| level | What it means | What to do |
|---|---|---|
same_photo | The same file, or the same picture shrunk or saved again. | Very likely a copy. Show it to a person. |
likely_same | The same picture edited: cropped, turned, straightened or brightened. | Probably a copy. Show it to a person. |
possibly_related | It looks alike, for example another photo of the same item from the same spot. | Worth a look. Not a finding. |
no_match | Nothing in your published photos is close. | It does not prove the photo is original. |
Each level comes from how close two photos' fingerprints are, so the same photo shrunk, cropped, turned, tilted or brightened is still found. Some edited copies are missed, and an unrelated photo can occasionally look similar, so a person always decides. A group is never matched with itself, and only published groups are searched.
| check | What a pass shows | How it can be faked |
|---|---|---|
real_tap | The shutter was pressed by a finger on the touch screen, not by a script. | A modified browser can report any tap. |
phone | The camera page ran in a phone or tablet browser with a touch screen. | Desktop browser tools can pretend to be a phone. |
camera_page_file | The file has no camera app details (make, model, location). Photos made by the camera page never have them, so it was not taken in another app and uploaded. | Anyone can remove those details from a photo. |
live_camera | The camera picture was live: it changed over time and arrived at 8 or more frames a second. | A virtual camera program playing a video. |
photo_from_camera | The photo looks like what the live camera showed a moment before (75% alike or more). | The same virtual camera showing a faked picture. |
motion_sensors | The phone's motion readings show the small shake of a hand. The seller has to allow motion for this one. | A modified browser can make up readings. |
real_camera | The camera's name is not a program that pretends to be a camera (OBS Virtual Camera, ManyCam and similar). | Renaming the program's camera. |
motion_matches_picture | How far the phone turned agrees with how far the picture moved. A picture that moves while the phone is still, or a phone that turns while the picture stays still, fails. | The hardest to fake: it needs made-up sensor readings that agree with a made-up video. |
To try the test upload, set CAPTURE_ID to the id returned by Start a group. The PNG file must be 640 to 4096 pixels on its longest side and no more than 8 MB. Upload as many photos as your minPhotos setting requires, then confirm them in upload order.
curl -X POST "https://business.pcific.ai/api/v1/pixel-trace/captures/$CAPTURE_ID/test-photos" \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: image/png" \
--data-binary @photo.pngcurl -X POST "https://business.pcific.ai/api/v1/pixel-trace/captures/$CAPTURE_ID/test-confirm" \
-H "Authorization: Bearer $PCIFIC_TEST_KEY"Every error is JSON with error (what went wrong), code (a fixed name to branch on), tryThis (the next step) and sometimes problems (the exact fields at fault). A 4xx means fix the request. A 5xx means a problem on PCIFIC's side: send the same request again. The errors every address shares (UNAUTHORIZED, KEY_NOT_ALLOWED, RATE_LIMITED, BODY_TOO_LARGE, BACKEND_UNAVAILABLE and the rest) are on the Errors, limits and retries page. With a live key, starting a group and checking a photo can also be refused with OUT_OF_CREDITS or SPEND_LIMIT_REACHED. A key without the right permission is refused with KEY_NOT_ALLOWED: starting, reading, publishing, retracting and checking need pixel-trace, and settings need settings.
Most errors also have a docs link to their own section on PixelTrace errors, with the usual causes, how to fix it and a request that works. That page starts with the mistakes integrations make most often.
Orbit photo uploads and PixelTrace checks and camera uploads share a photo capacity limit per server: 16 requests and 128 MB of uploaded bytes, with at most 2 requests and 64 MB per business across its shops and keys. These limits include receiving and processing. Content-Length reserves bytes before the body is read; without it the call reserves its maximum body size. Actual streamed bytes are counted too. Uploads must arrive within 120 seconds and wait at most 20 seconds for a photo worker. Public demos use separate capacity: 2 requests, 16 MB and one photo worker. Demo traffic cannot take paid photo places. A busy refusal does not save photos. Wait Retry-After seconds before sending the same upload again.
| Code | HTTP | Why it happens | How to fix |
|---|---|---|---|
PHOTO_UPLOAD_BUSINESS_BUSY | 429 | Your business has reached its photo request or byte capacity across Orbit and PixelTrace. | Wait the 20 seconds in Retry-After, then send the photo upload again. Send fewer uploads at the same time. |
PHOTO_UPLOAD_BUSY | 503 | The server's photo capacity is full, or a photo worker was not available within 20 seconds. | Wait the 20 seconds in Retry-After, then send the photo upload again. |
PHOTO_UPLOAD_TIMEOUT | 408 | The photo upload body did not finish arriving within 120 seconds. | Check your connection, then send the photo upload again. Send fewer photos per upload if needed. |
PIXEL_TRACE_TURNED_OFF | 403 | PixelTrace is switched off for this account (enabled is false). Groups already started carry on. | Switch it on in the dashboard, or with a test key that has the settings permission send {"enabled": true} to PUT /api/v1/pixel-trace/settings. |
PIXEL_TRACE_TOO_MANY_REQUESTS | 429 | More than 600 group starts and checks a minute for this account. | Wait the number of seconds tryThis names (it is also in the Retry-After header), then send the request again. |
INVALID_JSON | 400 | The body is not a JSON object. | Send a JSON object with the header "Content-Type: application/json". |
PIXEL_TRACE_LISTING_ID_INVALID | 400 | listingId is missing, empty, not text, or longer than 120 characters. | Send your own id for the listing these photos are for, for example "listingId": "listing-123". |
PIXEL_TRACE_LISTING_URL_INVALID | 400 | listingUrl is not a full https:// address (http:// only with a test key), or is over 2,000 characters. | Send the listing page's address as buyers see it, starting https://, or leave listingUrl out. |
NOT_FOUND | 404 | No group with this id in this shop for your kind of key (a test key reaches only test groups, a live key only live groups), the id is not a UUID, or the group expired and was deleted (its camera link ran out before the seller confirmed, or it was not published within a day of confirming). | Check the id from POST /api/v1/pixel-trace/captures and use the same kind of key that started it. If the group was deleted, start a new one. |
PIXEL_TRACE_NOT_CONFIRMED | 409 | The seller has not confirmed the group yet (status capturing), so it cannot be published. | Wait until GET /api/v1/pixel-trace/captures/{id} shows status ready, then publish it. |
PIXEL_TRACE_RETRACTED | 409 | The group was retracted, so it cannot be published. | Start a new group with POST /api/v1/pixel-trace/captures. |
PIXEL_TRACE_NO_PHOTO | 400 | The request body is empty. | Send the photo's bytes as the request body with a Content-Type of image/jpeg, image/png or image/webp, for example curl --data-binary @photo.jpg -H "Content-Type: image/jpeg". |
PIXEL_TRACE_CONTENT_TYPE | 415 | The Content-Type header for the check is missing or is not a supported image type. | Send a JPEG, PNG or WebP photo with Content-Type: image/jpeg, image/png or image/webp, and the photo's bytes as the request body. |
PIXEL_TRACE_PHOTO_REFUSED | 422 | The file is not a JPEG, PNG or WebP (HEIC is not accepted), is damaged or cut short, has fewer than 640 pixels on its longest side, is a JPEG over 60 megapixels, is a PNG or WebP over 25 megapixels, or is a CMYK or 16-bit JPEG. The message says which. | Read the message, then send the photo as a JPEG, PNG or WebP within the size limits. A HEIC photo from an iPhone needs converting to JPEG first. |
PIXEL_TRACE_TOO_LARGE | 413 | The photo is over the 15 MB limit for a PixelTrace check. | Send one photo of 15 MB or less. Resize it or save it at a lower quality, then send it again. |
PIXEL_TRACE_PHOTO_FAILED | 500 | Reading the photo failed on PCIFIC's side, not because of the photo. | Send the photo again. If it keeps happening, contact PCIFIC support. |
INVALID_SETTING | 400 | A setting has the wrong type or is out of range, or "mode" was sent with anything but "isolated". The message names it. | Send the setting in the form the message shows. Remove "mode": there is nothing to switch on. |
PIXEL_TRACE_PREFIX_RESERVED | 409 | codePrefix is kept for a brand, or for PCIFIC. | Choose different letters, for example this business's initials. |
PIXEL_TRACE_PREFIX_TAKEN | 409 | codePrefix is used, or was used, by another business. | Choose different letters. |
NOT_FOUND | 404 | No published or retracted group has this code. | Check the code, for example PCF-IMG-ABCDEF-234567. A group shows here only once the shop has published it. |
PIXEL_TRACE_TEST_KEY_ONLY | 403 | The call was made with a live key. Only a test key may add photos to a group or confirm it without the seller's phone. | Use a test key to test your integration. For a live group, send the seller the cameraUrl so they take the photos on their phone. |
PIXEL_TRACE_TEST_PHOTOS_FULL | 409 | The business already holds 200 test photos in its test groups that are not retracted, from uploads and phones together. | Test photos are deleted 24 hours after their group starts. Retract a test group you no longer need with POST /api/v1/pixel-trace/captures/{id}/retract, or try again later. |
PIXEL_TRACE_TEST_GROUPS_FULL | 409 | A test key was used, and the business already has 50 test groups that are not retracted. | Retract test groups you no longer need with POST /api/v1/pixel-trace/captures/{id}/retract. Test groups are also deleted 24 hours after they start. |
INTERNAL | 500 | Something went wrong on PCIFIC's side. | Try again in a moment. If it keeps happening, contact PCIFIC support. |
These come from the camera page on the seller's phone, not from your server, and the page shows each one with its next step. They are listed so your support team knows what a seller means. Most are fixed by reloading the page or taking the photo again. If a seller is stuck, start a new group and send a new link.
| Code | HTTP | Why it happens | How to fix |
|---|---|---|---|
PHOTO_UPLOAD_BUSINESS_BUSY | 429 | Your business has reached its photo request or byte capacity across Orbit and PixelTrace. | Wait the 20 seconds in Retry-After, then send the photo upload again. Send fewer uploads at the same time. |
PHOTO_UPLOAD_BUSY | 503 | The server's photo capacity is full, or a photo worker was not available within 20 seconds. | Wait the 20 seconds in Retry-After, then send the photo upload again. |
PHOTO_UPLOAD_TIMEOUT | 408 | The photo upload body did not finish arriving within 120 seconds. | Check your connection, then send the photo upload again. Send fewer photos per upload if needed. |
NOT_FOUND | 404 | This camera link does not exist, or has expired. | Ask your shop for a new camera link, then open the whole link they send you. |
INVALID_JSON | 400 | The camera page sent something that is not a JSON object. | Reload the camera page. If it keeps happening, ask the shop for a new link. |
PIXEL_TRACE_PHONE_ONLY | 403 | The browser is not on a phone or tablet. | Scan the QR code with your phone's camera, or open the link on your phone. |
PIXEL_TRACE_DEVICE_KEY_INVALID | 400 | The browser could not make its security key (deviceKey is not a P-256 public key), for example in a private window. | Open the link in Safari on iPhone or Chrome on Android, and make sure it is not a private window. |
PIXEL_TRACE_LINK_ALREADY_OPENED | 409 | Another browser already opened this link, and it works only there. | To carry on here, type the six characters shown under "Switch phone" on the page where you first opened it (POST .../move). If that was not you, ask the shop for a new link. |
PIXEL_TRACE_MOVE_CODE_WRONG | 403 | moveCode is not the code shown on the camera page where the link was first opened. | Check the six characters under "Switch phone" on that page, and type them again. |
PIXEL_TRACE_MOVE_LOCKED | 403 | Ten wrong move codes were typed for this camera link. Switching phones is locked for 15 minutes. | Try the code shown under "Switch phone" on the first phone again at the UTC time given in the response. You can keep taking photos on that phone while you wait. |
PIXEL_TRACE_CAMERA_NOT_OPENED | 409 | No camera cookie, or not the cookie of the phone that opened this link, or the seller already confirmed the group (the cookie is deleted then). The camera routes answer only the seller's phone browser that opened the camera link, never an API key on its own. | Open the camera link again on the phone you are taking photos on. If you finished, your photos are saved: go back to the shop. From your own server, read the group with GET /api/v1/pixel-trace/captures/{captureId}, and with a test key add photos with POST .../test-photos and confirm with POST .../test-confirm. |
PIXEL_TRACE_CAPTURE_CLOSED | 410 | The camera link expired, the group was cancelled or retracted, or the seller already confirmed the group. | Ask the shop for a new link. If the photos were already confirmed, go back to the shop to finish the listing. |
PIXEL_TRACE_SIGNATURE_INVALID | 403 | The signature was not made by the key of the phone the link is bound to. | Reload the camera page and try again. If it keeps happening, open the camera link again on the phone you started on. |
PIXEL_TRACE_SHOT_INVALID | 400 | The shutter press was sent without its id or signature (or the x-pixel-trace-shot header is missing or not a UUID). | Reload the camera page and take the photo again. |
PIXEL_TRACE_SHOT_INVALID | 403 | No unused shutter press in this group has this shot id and shot code. | Take the photo with the camera page's shutter button. |
PIXEL_TRACE_SHOT_USED | 409 | This shutter press was already used for a photo. | Take the photo again. If the answer to a photo was lost, send the same photo again to get the saved one. |
PIXEL_TRACE_SHOT_EXPIRED | 410 | The photo arrived more than 60 seconds after the shutter was pressed. | Take the photo again. If your signal is weak, move somewhere with better signal. |
PIXEL_TRACE_STILL_SENDING | 409 | Three other photos are still on their way to PixelTrace. | Wait a moment for them to finish sending, then take the next photo. |
PIXEL_TRACE_GROUP_FULL | 409 | The group already holds 12 photos. | Delete a photo you do not need, then take the new one. |
PIXEL_TRACE_UPLOAD_CUT_SHORT | 400 | The photo upload was cut short, so the form could not be read to the end. | Check your phone's signal and tap Retry. |
PIXEL_TRACE_UPLOAD_INCOMPLETE | 400 | The upload is missing the photo or its details. | Reload the camera page and take the photo again. |
PIXEL_TRACE_TOO_LARGE | 413 | The photo is over 8 MB, or its details are over 128 KB. | Reload the camera page and take the photo again. |
PIXEL_TRACE_EVIDENCE_INVALID | 400 | The camera page sent the photo's details in a form PixelTrace cannot read (not JSON, or too many live copies, frame times or motion readings). | Reload the camera page and take the photo again. |
PIXEL_TRACE_NOT_JPEG | 422 | The photo is not a JPEG, which is what the camera page makes. | Take the photo with the camera page's shutter button. |
PIXEL_TRACE_PHOTO_REFUSED | 422 | The photo is damaged, outside 640 to 4096 pixels on its longest side, over the pixel limit (60 megapixels for JPEG, 25 for PNG and WebP), or uses an unsupported colour format. The message says which. | Read the message, then take the photo again with the camera page's shutter button. |
PIXEL_TRACE_PHOTO_FAILED | 500 | Reading the photo failed on PCIFIC's side, not because of the photo. | Send the photo again. If it keeps happening, contact PCIFIC support. |
PIXEL_TRACE_STORAGE_FAILED | 502 | The photo could not be saved. This is a problem on PCIFIC's side. | Tap Retry in a moment. If it keeps happening, contact the shop. |
PIXEL_TRACE_SIGNING_KEY_UNAVAILABLE | 503 | The PixelTrace signing key on this server is no longer available. | Your photos are still saved. Wait a minute and confirm again. If this keeps happening, contact PCIFIC support. |
PIXEL_TRACE_CONFIRM_INVALID | 400 | photoIds is missing, or holds something that is not a UUID. | Reload the camera page and tap confirm again. |
PIXEL_TRACE_PHOTOS_CHANGED | 409 | photoIds is not exactly the photos saved in the group, each once. | Reload the camera page, check your photos and confirm again. |
PIXEL_TRACE_TOO_FEW_PHOTOS | 409 | The group has fewer photos than the shop's minPhotos setting. | Take more photos of the item, then confirm. |
NOT_FOUND | 404 | The photo is not in this group, or was already deleted. | Reload the camera page to see the photos saved. |
PIXEL_TRACE_TOO_MANY_REQUESTS | 429 | More than 240 camera calls a minute from this network. | Wait the number of seconds tryThis names (it is also in the Retry-After header), then send the request again. |
| What | Limit |
|---|---|
| Photos in a group | 12 at most, and at least your minPhotos (1 by default). |
| A camera link waiting to be opened | openWithinMinutes, 15 minutes by default. |
| The seller's time once they open the link | captureMinutes, 30 minutes by default. Reloading the page does not restart it. |
| A confirmed group waiting to be published | 1 day. Then it is deleted. |
| Wrong codes when moving to another browser | 10 per link, then it can no longer move. |
| A camera photo | A JPEG of up to 8 MB, from 640 to 4096 pixels on its longest side, up to 60 megapixels. |
| A shutter code | Works once, for 60 seconds. At most 3 photos can be on their way at once. |
| A photo sent to the check address | At least 640 pixels on the longest side, up to 15 MB. JPEG up to 60 megapixels, PNG or WebP up to 25 megapixels. HEIC is not accepted: convert it to JPEG first. |
| listingId | Your business's own listing id as text, not necessarily a UUID. After spaces at the ends are trimmed, 1 to 120 characters. listingTitle and marketplaceName are cut at 120. |
| listingUrl | Up to 2,000 characters, https:// (a test key may use http://). |
| Links to photo files | Work for 10 minutes. Read the group again for new ones. |
| Matches per photo | 10 at most. |
| Starting groups and checking photos, per account | 600 a minute across all keys (PIXEL_TRACE_TOO_MANY_REQUESTS). Photo checks have their own 600-per-minute key allowance. Group starts still share the 120-per-minute work allowance with other tools. |
| Photo-check requests, per key | 600 a minute (RATE_LIMITED), separate from AI work starts. Invalid photos also count. One key can use all 600 account requests for checks when no groups are started. |
| Camera calls, per network | 240 a minute. 20 links opened a minute. |
| Shutter presses and uploads, per group | 40 a minute each. |
| Reading certificates, per network | 120 a minute. |
| Badge images, per network | 600 a minute. Browsers keep a badge for 5 minutes. |
| Time the website waits for PCIFIC | Up to 60 seconds for a photo check, publishing and camera uploads, 15 seconds for the rest. |
| Word | Meaning |
|---|---|
| group | The photos of one listing, taken together. It has an id and a public code. |
| camera link | The address of PCIFIC's camera page for one group. It opens on one phone (the first one to open it). |
| QR code | A square picture a phone camera scans to open a link. Show the camera link as one on a computer screen. |
| trust level | strong, normal or weak, from eight checks of how a photo was taken. Evidence, not proof. |
| manifest | The signed record of a group: its code, its listing, when it was confirmed, and each photo's id, order, file hash and trust level. |
| signature | A value only PCIFIC's secret key can make for exactly this manifest. Anyone can check it with the public key. |
| JWKS (JSON Web Key Set) | A list of public keys, here every key that checks PixelTrace signatures, at /.well-known/jwks.json. |
| Ed25519 | The kind of signature used. Node can check it with no extra package. |
| SHA-256 hash (sha256) | A fingerprint of a file's exact bytes. Change one byte and the hash changes. |
| fingerprint | A short number made from how a picture looks, so a shrunk, cropped or brightened copy still lands close to the original. |
| publish | Calling complete once the listing is live. It compares the photos and opens the public certificate. |
| retract | Taking a group back. Its fingerprints and photo files are deleted and its certificate says it was retracted. |
| certificate | The public page for a code: the listing, each photo's preview and trust level, and the signed manifest. |
| idempotency key (a header that makes a retry safe) | Send Idempotency-Key on every POST, PUT and DELETE, including photo checks and camera actions, so a retry after a timeout does not repeat work or credits. See Errors, limits and retries. |
| test key and live key | A test key only sees test groups, which match only other test groups, spend no credits, get a certificate marked as a test and are deleted 24 hours after they start. A live key only sees live groups and spends credits. |