Score a listing from 0 to 10 the way a careful buyer would, see exactly what to fix, and ask the seller for what is missing.
Safeguard reads a listing the way a careful buyer would before spending their own money, scores it from 0 to 10 for how ready a buyer could be to buy it exactly as it stands, and tells the seller what to fix.
| Kind of business | What you send | What you do with the result |
|---|---|---|
| Shops, before publish | The seller's draft listing, in your fields, and its photos. | Show the score and the fixes on the publish screen. Publish when the verdict means pass, hold the rest, and let the seller improve the listing in place. |
| Insurance claims (listings and evidence descriptions) | The written description of a claimed item or its evidence, in a category with fields such as make, model, serial number, date bought, value and what happened, plus the photos. | Tell the claimant what is missing before they submit, and sort claims for the handler by verdict. Safeguard checks the description is complete and agrees with itself and the photos. It does not decide whether a claim is true. |
| Rentals and holiday lets | A host's listing: rooms, amenities, house rules, deposit, cancellation, area. | Ask the host for the facts a guest would want in one set of questions, and put the FAQ on the listing page. |
| Car dealers | An advert: make, model, year, mileage, service history, previous owners, faults. | Flag missing service history or mileage before the advert goes live, and offer a clearer title in your house style. |
| Resale and second hand (phones, clothes, bags) | A seller's listing with size, condition, flaws and what is included, plus photos. | Ask the seller for the missing size or battery health. Safeguard does not authenticate an item: a complete listing of a fake still scores well. |
| Classifieds and imported feeds | Listings that arrive from a partner feed or a bulk upload, with no seller present. | Review each one in the background and send the low verdicts to a person. Seller questions need a seller, so ignore them for feeds. |
| Tickets and services | Date, venue, seat or area, what is included, refund terms. Set requirePhotos to false for a text-only review. | Check the details a buyer needs are stated before the offer goes live. |
Safeguard also gives the buyer side something to show: questions a careful buyer would still ask (questions), a buyer FAQ written from the listing (faq), and suggested new wording (rewrite).
A review is kept for 30 minutes after its last change (started, finished, answered or retried), then deleted. Save the score and the updated listing on your side before then. Safeguard never saves your listing for you.
These steps assume a test key in $PCIFIC_TEST_KEY and a published Phones category like the one in Set up your listing fields. The price is a money field: amount is in minor units (pence or cents, so 31500 with GBP is £315) and currency is your shop's currency.
curl https://business.pcific.ai/api/v1/safeguard/settings \
-H "Authorization: Bearer $PCIFIC_TEST_KEY"{
"enabled": true,
"minimumScore": 7,
"webSearch": true,
"bannedItems": [],
"instructions": null,
"orbitDefault": false,
"verdicts": [
{
"name": "High risk",
"from": 0,
"meaning": "block"
},
{
"name": "Caution",
"from": 4,
"meaning": "review"
},
{
"name": "Looks good",
"from": 7,
"meaning": "pass"
}
],
"categoryVerdictStarts": {}
}At least one photo is required by default on POST /api/v1/safeguard/reviews, including when the listing comes from orbitRunId. A request with no photos is refused before work with PHOTOS_REQUIRED (422), with no charge. Strongly recommended: keep photos on. Set requirePhotos to false only when you choose a text-only request, for example for a service. Without photos results are less reliable and can vary more. Send each photo as a file in a multipart form (a form that carries files). You get a photo id for each, which lives for 30 minutes. Orbit's page has the full rules for photos.
curl -X POST https://business.pcific.ai/api/v1/images \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-F "photo=@front.jpg"{
"images": [
{
"id": "3f1c2a9e-5b7d-4c1e-9a52-7d0c6e8b1f34",
"width": 3024,
"height": 4032,
"preview": "https://files.example/preview.webp",
"listing": {
"url": "https://files.example/listing.webp",
"format": "webp"
},
"expiresAt": "2026-10-19T10:30:00Z",
"fileName": "front.jpg"
}
]
}curl -X POST https://business.pcific.ai/api/v1/safeguard/reviews \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{"category":"Phones","listing":{"title":"iPhone 13 128GB, good condition, works fine","description":"Selling my old phone. No box. Collection preferred.","brand":"Apple","model":"iPhone 13","storage_gb":128,"condition":"Good","price":{"amount":31500,"currency":"GBP"}},"images":["3f1c2a9e-5b7d-4c1e-9a52-7d0c6e8b1f34"],"requirePhotos":true,"review":true,"faq":false}'Before work starts, Safeguard refuses wrong field types, values outside fixed options and unreadable prices with 400 INVALID_LISTING. problems has one entry per affected field, with path, problem and tryThis. No credits are spent. Missing details, including required fields, and unusual values that have the right type and are allowed still go to the AI judge. A price amount containing a decimal point or exponent, including 749.0, is refused. Send whole minor units, like 4500 pence for £45. Any price that cannot be read in a result is explained in pcific.priceProblems with path, problem and tryThis.
{
"status": "working",
"reviewId": "1f9380fa-a135-4b3d-ab70-1dc1fc50f065",
"tryThis": "Read the review with GET /api/v1/safeguard/reviews/1f9380fa-a135-4b3d-ab70-1dc1fc50f065 in a few seconds.",
"warnings": []
}The addresses inside tryThis and retry already use the public /api/v1/ prefix. Call them as returned.
curl https://business.pcific.ai/api/v1/safeguard/reviews/1f9380fa-a135-4b3d-ab70-1dc1fc50f065 \
-H "Authorization: Bearer $PCIFIC_TEST_KEY"{
"status": "working",
"reviewId": "1f9380fa-a135-4b3d-ab70-1dc1fc50f065",
"tryThis": "Read this again in a few seconds."
}{
"status": "done",
"reviewId": "1f9380fa-a135-4b3d-ab70-1dc1fc50f065",
"score": 6,
"passed": false,
"minimumScore": 7,
"verdict": {
"name": "Caution",
"meaning": "review"
},
"banned": {
"isBanned": false,
"reason": null
},
"review": {
"summary": "Clear model and storage, but the battery health and lock status are missing.",
"fixes": [
{
"field": "/battery_health",
"importance": "important",
"problem": "Battery health is not stated.",
"fix": "Add the battery health percentage from Settings."
},
{
"field": "/description",
"importance": "important",
"problem": "It does not say whether the phone is unlocked.",
"fix": "Say whether the phone is unlocked or tied to a network."
}
]
},
"questions": [
"Has the screen or battery ever been replaced?"
],
"sellerQuestions": [
{
"id": "q1",
"question": "What is the battery health percentage?",
"field": "/battery_health"
},
{
"id": "q2",
"question": "Is the phone unlocked or tied to a network?",
"field": "/description"
}
],
"listing": {
"title": "iPhone 13 128GB, good condition, works fine",
"description": "Selling my old phone. No box. Collection preferred.",
"brand": "Apple",
"model": "iPhone 13",
"storage_gb": 128,
"condition": "Good",
"price": {
"amount": 31500,
"currency": "GBP"
}
},
"pcific": {
"release": "2.0.0",
"category": "Phones",
"schemaVersion": 3,
"prices": {
"/price": {
"amount": 31500,
"currency": "GBP",
"display": "£315.00"
}
},
"warnings": []
},
"expiresAt": "2026-10-19T10:30:00Z"
}Here the phone scores 6 because two important facts are missing, so passed is false against a minimum of 7 and the verdict is Caution, which means review. Show the fixes to the seller and ask the questions in sellerQuestions.
A fix carries field only when the seller would change that field's value. Photo fixes and fixes about the listing as a whole are general suggestions with no field, for example "Add a clear photo of the back." Show them under the photos or as a note on the listing. A fix that needs uploaded or replacement photos carries target: photos. Use that target to open your photo uploader; do not write the photo request into /description. Photo seller questions can carry the same target with no field. Upload the requested photos, then start a new review using the returned image ids.
curl -X POST https://business.pcific.ai/api/v1/safeguard/reviews/1f9380fa-a135-4b3d-ab70-1dc1fc50f065/answers \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{"answers":[{"id":"q1","answer":"91%"},{"id":"q2","answer":"It is unlocked, works on any network"}]}'{
"status": "working",
"reviewId": "1f9380fa-a135-4b3d-ab70-1dc1fc50f065",
"tryThis": "Read the review with GET /api/v1/safeguard/reviews/1f9380fa-a135-4b3d-ab70-1dc1fc50f065 in a few seconds.",
"warnings": []
}Read the review again as in step 4. When it is done it holds the updated listing and a new score. The seller's answers changed two fields, and changes shows the seller's exact words for each:
{
"status": "done",
"reviewId": "1f9380fa-a135-4b3d-ab70-1dc1fc50f065",
"score": 9,
"passed": true,
"minimumScore": 7,
"verdict": {
"name": "Looks good",
"meaning": "pass"
},
"banned": {
"isBanned": false,
"reason": null
},
"review": {
"summary": "Clear model, storage, battery health and lock status. Only the charger is not mentioned.",
"fixes": [
{
"importance": "minor",
"problem": "It does not say whether a charger is included.",
"fix": "Say whether the charger is included."
}
]
},
"questions": [],
"sellerQuestions": [],
"changes": [
{
"field": "/battery_health",
"before": null,
"after": 91,
"questionId": "q1",
"quote": "91%"
},
{
"field": "/description",
"before": "Selling my old phone. No box. Collection preferred.",
"after": "Selling my old phone. Unlocked, works on any network. No box. Collection preferred.",
"questionId": "q2",
"quote": "It is unlocked, works on any network"
}
],
"listing": {
"title": "iPhone 13 128GB, good condition, works fine",
"description": "Selling my old phone. Unlocked, works on any network. No box. Collection preferred.",
"brand": "Apple",
"model": "iPhone 13",
"storage_gb": 128,
"condition": "Good",
"battery_health": 91,
"price": {
"amount": 31500,
"currency": "GBP"
}
},
"pcific": {
"release": "2.0.0",
"category": "Phones",
"schemaVersion": 1,
"prices": {
"/price": {
"amount": 31500,
"currency": "GBP",
"display": "£315.00"
}
},
"warnings": []
},
"expiresAt": "2026-10-19T10:30:00Z"
}Send a JSON object (text in curly brackets) with the header Content-Type: application/json. Keys Safeguard does not know are ignored, and each is named in warnings so a typo does not pass silently. Your listing keeps your own field names. Safeguard asks for no field in particular, not even a title or description, and judges against the fields of the category you name.
| Field | Type | Required | What it is |
|---|---|---|---|
category | string | No | The listing's category, named as in the latest published version of your listing fields. Required, unless you send orbitRunId. null is the same as leaving it out. |
listing | object | No | Customer-defined: the listing in your own field names, exactly as you send them. null or left out only with orbitRunId, which supplies the listing. Up to 50,000 characters once written as JSON. Required, unless you send orbitRunId. Photo and listing-field problems are collected together. A value of the wrong type, or one outside a field's allowed options, is refused with a 400 error that names each field in problems. Required fields are judged, not validated. A missing required field is accepted (202); the judge flags it and can ask the seller. Safeguard asks for no field in particular: not even a title or a description. |
listing.price | object | No | A price field (one marked x-pcific-money in your schema; this one is called price) is {amount, currency}. Leave it out when the listing has no price. |
listing.price.amount | integer | No | A whole number in minor units (pence or cents, so 31500 with GBP is £315). Decimal points and exponents, including 749.0, are refused; send 4500 pence for £45. Text amounts are refused too. Any other amount (fractional, negative, missing or out of range) is refused with a 400 error that names it in problems. |
listing.price.currency | string | No | The currency code. Prices are never converted, so it must be your shop's currency (the currency setting in the Orbit settings). |
images | string[] | No | Up to 12 photo ids from POST /api/v1/images. At least one is required by default, including when using orbitRunId. A photo is kept for 30 minutes after upload. The same id twice counts once. null is the same as leaving it out. |
requirePhotos | boolean | No | Default true on POST /api/v1/safeguard/reviews, also when null. With no photos the request is refused before work, with no charge. Strongly recommended: keep photos on. Set false to allow a text-only request, for example for a service. Without photos results are less reliable and can vary more. |
sellerNotes | string | No | What the seller wrote or said about the item, up to 5,000 characters. It counts as the seller's own words. null is the same as leaving it out. |
orbitRunId | string | No | null is the same as leaving it out. The runId of a finished Orbit listing less than 30 minutes old. Its listing, category, photos and the seller's words are used. listing, category and images, when sent too, replace Orbit's. sellerNotes is added to what the seller told Orbit. |
review | boolean | No | true (the default, also when null) scores the listing and returns the fixes and questions. false leaves them out, so send faq or rewrite with it. |
faq | boolean | No | true adds 3 to 6 buyer questions and answers. Default false, also when null. The review keeps web search when enabled; FAQ answers never use web pages. |
rewrite | boolean | string[] | No | true suggests new wording for /title and /description. Instead of true you can send a list of 1 to 5 field paths, each once and none inside a list, like ["/title"]. Default false. |
modelTier | string | No | standard (the default) or ultra-fast. Ultra Fast answers in about half a second instead of several, and a live use spends more credits: the Credits page in your dashboard shows how many. A use is charged as Ultra Fast only when Ultra Fast answered the whole request; otherwise Standard answers and it is charged as Standard. A business on its own AI key is answered by its own model. null is the same as leaving it out. |
INVALID_FIELD (400) means a field has the wrong type or size. The error names it, and problems lists each place with a next step.
| Field | Why it is refused |
|---|---|
listing | More than 50,000 characters as JSON, or not a non-empty object (that one is LISTING_REQUIRED). |
category | Not text. |
images | Not a list of photo ids (UUIDs, the ids POST /api/v1/images answers with), or more than 12. |
sellerNotes | Not text, or longer than 5,000 characters. |
orbitRunId | Not the runId of an Orbit listing. |
review, faq, requirePhotos | Not true or false. |
rewrite | Not true, false or a list of 1 to 5 field paths, or a path named twice, or a field inside a list (a list's entries each have their own value, so there is no one value to rewrite). |
answers | Not a non-empty list of {id, answer}, an answer that is not text or is over 1,000 characters, or the same question answered twice in one request. |
Photo problems and listing-field problems are collected together in problems. A photo error keeps its 422 code; otherwise listing errors use 400 INVALID_LISTING. Required fields are judged, not validated. A missing required field is accepted (202); the judge flags the gap and can ask the seller. Each value supplied in listing must have the type your listing fields set. Text where a number belongs ("ninety"), a word where true or false belongs ("yes"), or a choice that is not one of the allowed options is refused with a 400 error, before any review starts. Nothing is charged. problems names each field, what is wrong with it and what to send instead. Missing details and unusual values of the right type that satisfy any fixed options are not refused: Safeguard mentions them in the review.
A price amount is a whole number in the smallest unit of the currency. Decimal points and exponents, including 749.0, are refused; send 4500 pence for £45. Text amounts are refused too. Any other amount (fractional, negative, missing or out of range) is refused in the same way, and problems names the price.
Starting, answering and retrying all answer 202 with the same four fields. Fields not listed may be added at any time; ignore what you do not use.
| Field | Type | Required | What it is |
|---|---|---|---|
status | string | Yes | Always working. |
reviewId | string | Yes | The review. Read it with GET /api/v1/safeguard/reviews/{reviewId}. |
tryThis | string | Yes | What to do next: read the review again in a few seconds. |
warnings | string[] | Yes | One sentence for each key in your request that Safeguard does not know and ignored. Empty when there are none. |
GET /api/v1/safeguard/reviews/{reviewId} answers 200 with one of three shapes, chosen by status.
| Field | Type | Required | What it is |
|---|---|---|---|
status | string | Yes | working. |
reviewId | string | Yes | The review. |
tryThis | string | Yes | Read the review again in a few seconds. |
| Field | Type | Required | What it is |
|---|---|---|---|
status | string | Yes | done. |
reviewId | string | Yes | The review. |
modelTier | object | No | When the review is done. Which tier answered: {requested, handledBy, message}. |
modelTier.requested | string | Yes | standard or ultra-fast, as the request asked. |
modelTier.handledBy | string | Yes | The tier that answered the whole request: standard, ultra-fast, or byo when your own AI key answered. A use is charged as Ultra Fast only when this is ultra-fast. |
modelTier.message | string | Yes | Why handledBy differs from requested, else null. |
score | integer | No | 0 to 10, how ready a buyer could be to buy it as it stands. Checking the same listing again can give a slightly different score, normally within 1 point. Only when review is on. |
passed | boolean | No | true when score reached minimumScore, the item is not banned, and no fix is important or warning. A listing with an important or warning fix never passes, even at or above minimumScore. Only when review is on. |
minimumScore | integer | No | Your minimumScore setting when the review ran, 0 to 10. Only when review is on. |
verdict | object | No | Your own name for where the score falls. When passed is false because of an important or warning fix, it is your best verdict that is not a pass and that the score reaches. Only when review is on. |
verdict.name | string | No | The verdict's name, written by you in your settings. The standard names are High risk, Caution and Looks good. Show this to people. |
verdict.meaning | string | No | block, review or pass. Branch your code on this, never on the name. |
banned | object | No | When review or rewrite is on. The AI judge flags banned items and scams from evidence in the listing. |
banned.isBanned | boolean | No | true when the evidence shows the item is on your banned list, or is plainly illegal or a scam. The score is then 0. |
banned.kind | string | No | banned_item when the item is on your banned list or illegal to sell; scam when the listing shows clear deception. Only present when flagged. |
banned.reason | string | No | Required plain reason pointing to evidence in the listing when flagged. null when isBanned is false. |
review | object | No | Only when review is on. |
review.summary | string | No | One or two plain sentences saying what the score is for, up to 600 characters. |
review.fixes | object[] | No | Up to 10, each {field, target, importance, problem, fix}. field is the path of the field whose value the seller would change, like /battery_health. Photo fixes and fixes about the listing as a whole are general suggestions with no field. Show them under the photos or as a note on the listing. A fix needing uploaded or replacement photos carries target: photos, with no field. importance is important (a buyer would not pay without it), minor (a buyer would still buy) or warning (a warning sign, which makes the score 3). Empty at a score of 10. |
questions | string[] | No | Up to 6 things a careful buyer would still ask the seller. Can be empty. |
sellerQuestions | object[] | No | Up to 6 questions only the seller can answer, each {id, question, field?, target?, options?}. field is the path the answer fills. Photo requests can carry target: photos with no field, rather than /description. Use the photo uploader and start a new review with its returned image ids. options is that field's list of allowed values when it has a short list. Send the answers with POST .../answers. |
sources | object[] | No | Up to 6 web pages the judge relied on, each {url, title, finding}. Only when it used some. |
faq | object[] | No | Only with faq on. 3 to 6 entries {question, answer, needsSeller}. A question the listing does not answer has answer null and needsSeller true: ask the seller. |
rewrite | object[] | No | Only with rewrite on. One {field, before, after} for each field asked for, or [] when flagged. The listing is not changed. |
rewriteSkippedReason | string | No | Present when a flag prevents requested rewrites. Explains why they were skipped, including the flag reason. |
changes | object[] | No | After answers: {field, before, after, questionId, quote}. Any relevant published field can change, including title and description. supportingAnswers: [{questionId, quote}] names other contributing answers, including earlier ones. Unchanged values are omitted. You choose what to accept. |
listing | object | No | The listing as it now stands, with the seller's answers in it. Safeguard never saves it: you decide whether to. |
pcific | object | No | Facts about this review. |
pcific.release | string | No | The Safeguard release that ran, 2.0.0. |
pcific.category | string | No | The category as named in your schema. |
pcific.schemaVersion | integer | No | The version of your listing fields the review used. |
pcific.model | string | No | The id of the AI model that wrote the review, or the FAQ when only a FAQ was asked for. Empty text when no model ran. |
pcific.priceProblems | object[] | No | Any price that could not be read in the result, with path, problem and tryThis. Invalid submitted prices are refused before work starts. |
pcific.priceProblems.path | string | Yes | Where the price is in the listing, like /price. |
pcific.priceProblems.problem | string | Yes | What is wrong with how it is written. |
pcific.priceProblems.tryThis | string | Yes | How to write it instead. |
pcific.prices | object | No | Each price Safeguard read in the listing, by field path, as {amount, currency, display}. Only whole numbers written without decimal points or exponents are accepted. Any unreadable result price is explained in pcific.priceProblems. |
pcific.prices.{field} | object | Yes | One price, keyed by its field path like /price. |
pcific.prices.{field}.amount | integer | Yes | A whole number in the smallest unit. |
pcific.prices.{field}.currency | string | Yes | Your shop's currency code. |
pcific.prices.{field}.display | string | Yes | The price written for people, like £315.00. |
pcific.warnings | string[] | No | The warnings from the request, kept with the review. |
pcific.orbitRunId | string | No | Only when the review is of an Orbit listing. |
expiresAt | string | No | When the review is deleted: 30 minutes after its last change. |
With review set to false there is no score, passed, verdict, review, questions or sellerQuestions. A rewrite still includes banned so a flagged listing never receives polished wording. You also get listing and pcific.
| Field | Type | Required | What it is |
|---|---|---|---|
status | string | Yes | failed. This answer is HTTP 200: the failure is inside error. |
reviewId | string | Yes | The review. |
error | object | Yes | {error, code, tryThis}: what went wrong, a fixed code to branch on, and the next step. |
retry | string | Yes | The call that tries the same review again, like POST /api/v1/safeguard/reviews/{reviewId}/retry. Call this address as returned. |
{
"status": "failed",
"reviewId": "1f9380fa-a135-4b3d-ab70-1dc1fc50f065",
"error": {
"error": "Safeguard took over 180 seconds on this review.",
"code": "SAFEGUARD_TIMED_OUT",
"tryThis": "Try it again with POST /api/v1/safeguard/reviews/1f9380fa-a135-4b3d-ab70-1dc1fc50f065/retry. If it keeps happening, contact PCIFIC support with the reviewId."
},
"retry": "POST /api/v1/safeguard/reviews/1f9380fa-a135-4b3d-ab70-1dc1fc50f065/retry"
}The judge goes through this scale in order and stops at the first line that fits. An important gap is something a careful buyer would not pay without knowing. A minor gap is something they would like to know but would still buy without. Every fix in review.fixes is one gap or warning sign, marked important, minor or warning, and the score must match them.
| Score | When |
|---|---|
0 | The item is on your banned list, or the listing is plainly a scam or illegal. Only on evidence in the listing, the photos or the seller's words. |
1 | You cannot tell what is for sale at all. |
2 | You can tell the kind of item but not which one, like "iPhone" with no model. |
3 | A real warning sign: a claim the photos or the listing itself contradict, a price far from what this item sells for with nothing to explain it, or payment terms that look like a scam. |
4 | It is clear what the item is, but almost nothing else is said. |
5 | Three or more important gaps. |
6 | Two important gaps. |
7 | One important gap. |
8 | No important gaps, three or more minor ones. |
9 | No important gaps, one or two minor ones. |
10 | Nothing missing and nothing wrong. |
Two settings decide how a score is shown. They are set once for your shop, not on each request. The full settings table is on Shop configuration.
| Field | Type | Required | What it is |
|---|---|---|---|
enabled | boolean | No | Safeguard on or off. Default true. |
minimumScore | integer | No | The score a listing needs for passed, 0 to 10. Default 7. |
webSearch | boolean | No | Whether the AI may search the web (at most 2 searches a review). Default true. |
bannedItems | string[] | No | Up to 50 plain descriptions, each 1 to 200 characters, of what may not be listed. Plainly illegal items and scams are always banned. |
instructions | string | No | What your buyers care about, up to 2,000 characters. null clears it. It cannot change the scale. |
voice | string | No | One of PCIFIC's six voices: standard, warm, upbeat, formal, concise or reassuring. null uses your shop's voice. |
orbitDefault | boolean | No | Review every Orbit listing that does not say otherwise. Default false. |
verdicts | object[] | No | 2 to 5 verdicts, lowest first, each {name, from, meaning}. null puts back the standard three: High risk from 0 (block), Caution from 4 (review), Looks good from 7 (pass). |
categoryVerdictStarts | object | No | A category's own start scores, one for each verdict, like {"Art": [0, 6, 9]}. Only categories in your latest published listing fields. {} or null for none. |
When the judge finds a gap only the seller can fill, it adds a question to sellerQuestions with an id, the field the answer fills, and that field's list of allowed values (options) when it has a short one. Show all of them on one screen and send all the answers in one request.
Add "faq": true to also get 3 to 6 questions a buyer would ask on the listing page, each answered only from the listing, the photos and the seller's words, never from the web or a guess. A question the listing does not answer comes back with answer null and needsSeller true, so you can ask the seller. The review keeps web search when enabled. A separate FAQ writer sees only the listing, the photos and the seller's words, so FAQ answers never use web pages.
Add "rewrite": true to get suggested new wording for /title and /description, written in the listing style you set in the Orbit settings and kept within each field's limits in your schema. Name the fields yourself with "rewrite": ["/title"] (1 to 5 field paths, each once, none inside a list). Every suggestion has before and after, and the listing itself is not changed. When the AI flags the listing, rewrite is [] and rewriteSkippedReason explains why, including the flag reason. banned.kind is banned_item for an item on your banned list or illegal to sell, or scam for clear deception. banned.reason is required and points to the evidence in plain words. Spelling, grammar, formatting and missing details alone are never scam evidence. Send "review": false with either to get only the FAQ or the rewrite.
curl -X POST https://business.pcific.ai/api/v1/safeguard/reviews \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{"category":"Phones","listing":{"title":"iPhone 13 128GB, good condition, works fine","description":"Selling my old phone. No box. Collection preferred.","brand":"Apple","model":"iPhone 13","storage_gb":128,"condition":"Good","price":{"amount":31500,"currency":"GBP"}},"requirePhotos":false,"review":false,"rewrite":["/title"]}'{
"status": "done",
"reviewId": "1f9380fa-a135-4b3d-ab70-1dc1fc50f065",
"banned": {
"isBanned": false,
"reason": null
},
"rewrite": [
{
"field": "/title",
"before": "iPhone 13 128GB, good condition, works fine",
"after": "Apple iPhone 13 128GB, Good Condition"
}
],
"listing": {
"title": "iPhone 13 128GB, good condition, works fine",
"description": "Selling my old phone. No box. Collection preferred.",
"brand": "Apple",
"model": "iPhone 13",
"storage_gb": 128,
"condition": "Good",
"price": {
"amount": 31500,
"currency": "GBP"
}
},
"pcific": {
"release": "2.0.0",
"category": "Phones",
"schemaVersion": 1,
"prices": {
"/price": {
"amount": 31500,
"currency": "GBP",
"display": "£315.00"
}
},
"warnings": []
},
"expiresAt": "2026-10-19T10:30:00Z"
}Orbit writes a listing from a seller's photos and notes. Safeguard can review it two ways, and in both it is told where Orbit got each value, so it can say plainly when Orbit added a fact nobody gave.
curl -X POST https://business.pcific.ai/api/v1/safeguard/reviews \
-H "Authorization: Bearer $PCIFIC_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{"orbitRunId":"8c7a6f0e-2b4d-4e51-a9c3-5d1e7f2b9a40"}'Every error is JSON with error (what went wrong), code (a fixed name your code can branch on), tryThis (the next step) and sometimes problems (each exact place at fault, as a path like /images/0 with its own next step). 4xx means fix the request. 5xx means a problem on PCIFIC's side: wait and try again.
Most errors also have a docs link to their own section on Safeguard errors, with the usual causes, how to fix it and a request that works. That page starts with the mistakes integrations make most often.
Errors reach you in two places. Refused straight away: the call itself answers with an HTTP error, no review is started, and nothing is saved. Failed later: the review was accepted (202), then went wrong while it worked. Then GET answers HTTP 200 with status failed and the error inside, so read status before you read anything else.
| Code | HTTP | Why it happens | How to fix |
|---|---|---|---|
SAFEGUARD_TURNED_OFF | 403 | Safeguard is switched off for this account. Starting a review, sending answers and retrying are all refused while it is off. | Switch it on in the dashboard, or send {"enabled": true} to PUT /api/v1/safeguard/settings with a test key that has the settings permission. |
MODEL_TIER_INVALID | 400 | modelTier is not standard or ultra-fast. | Send modelTier: standard (the default) or ultra-fast, or leave it out. |
LISTING_REQUIRED | 400 | The request has no listing to review: "listing" is missing or empty, and there is no orbitRunId. If you sent "item" instead, the next step asks if you meant "listing". | Send the listing in your own field names, like "listing": {"title": "iPhone 14 128GB", "description": "..."}, or the runId of a finished Orbit listing in "orbitRunId". |
CATEGORY_REQUIRED | 400 | The request does not say the listing's category, so Safeguard does not know its fields. | Send the category name as it is in your schema, like "category": "Phones". |
SAFEGUARD_NO_SCHEMA | 409 | Your business has no published listing fields, so Safeguard does not know the listing's fields. | Upload a category with PUT /api/v1/schema-draft/categories/{category}, then publish with POST /api/v1/schema-versions. See Set up your listing fields. |
CATEGORY_NOT_FOUND | 422 | The latest version of your published listing fields has no category with this name. | Check the name against GET /api/v1/schema-versions/{version} and send it as written there. |
PHOTOS_REQUIRED | 422 | The review request has no photos in images. No work starts and no credits are spent. | Upload at least one photo with POST /api/v1/images and send its id in images, or set requirePhotos: false. Keeping photos on is strongly recommended: without them results are less reliable and can vary more. |
PHOTOS_NOT_FOUND | 422 | A photo id in "images" is unknown, belongs to another business, or has expired (photos are kept for 30 minutes). problems has one entry for each, like /images/1. | Upload each photo listed again with POST /api/v1/images, and use the new ids. |
CURRENCY_NOT_IN_SCHEMA | 422 | A price field in the category lists the currencies it allows, and your business's currency is not one of them. Prices are never converted. | Add your currency to that field's currency options and publish the listing fields again, or change the currency setting with PUT /api/v1/orbit/settings. |
INVALID_LISTING | 400 | A listing field has the wrong type, is outside its fixed allowed values, or has an unreadable price. problems lists each affected field under /listing with its own next step. No work starts and no credits are spent. Required fields are judged, not validated: a missing required field is accepted (202), then flagged by the judge with seller questions where needed. | Correct each field listed in problems, then send the listing again. |
NOTHING_TO_DO | 400 | The request switched the review off ("review": false) and asked for neither a FAQ nor a rewrite. | Leave "review" out (it is on by default), or send "faq": true or "rewrite": true. |
REWRITE_NO_FIELDS | 422 | "rewrite": true rewrites /title and /description, but this category has neither field. | Name the fields to rewrite by their paths, like "rewrite": ["/name", "/details"]. |
UNKNOWN_FIELD | 422 | A path in "rewrite" is not a field of this category's schema. problems has one entry for each, like /rewrite/0. | Use field paths from your schema, like /title. |
ORBIT_LISTING_NOT_FOUND | 404 | There is no Orbit listing with this orbitRunId for your business, or it was deleted: Orbit listings are kept for 30 minutes after their last change. | Send the listing itself in "listing", with its "category", instead of orbitRunId. |
ORBIT_LISTING_NOT_READY | 409 | The Orbit listing is still working or waiting for the seller's answers, so there is nothing to review yet. | Read it with GET /api/v1/listings/{runId} and review it once it is listed. |
ORBIT_LISTING_UNREADABLE | 500 | The saved Orbit listing could not be read back. This is a problem on PCIFIC's side. | Send the listing itself in "listing" with its "category" instead of orbitRunId. If it happens again, contact PCIFIC support with the runId. |
SAFEGUARD_TURNED_OFF applies here too. Reading a review with an id that is unknown, belongs to another business or is older than 30 minutes is NOT_FOUND (404): start a new review.
| Code | HTTP | Why it happens | How to fix |
|---|---|---|---|
REVIEW_NOT_READY | 409 | The call does not fit where the review is. Answers are taken only when the review is done, and a retry only when it failed. A review that is still working takes neither. | Read the review with GET /api/v1/safeguard/reviews/{reviewId} and do what its status says. |
NO_OPEN_QUESTIONS | 409 | The review has no questions waiting for the seller. Either it asked none, or they were all answered. | To review a changed listing, start a new review with POST /api/v1/safeguard/reviews and the updated listing. |
TOO_MANY_ANSWER_ROUNDS | 409 | The seller has already sent answers 3 times for this review, the most allowed. | Start a new review of the updated listing (its "listing" field) with POST /api/v1/safeguard/reviews. |
UNKNOWN_QUESTION | 422 | An answer's id is not one of the review's open questions. problems has one entry for each, like /answers/0/id. | Answer each question by the "id" it came with in "sellerQuestions". A question already answered is not open any more. |
These sit in error.code of a failed review. The HTTP column is the kind of fault, because the GET itself answers 200. SAFEGUARD_TURNED_OFF and CURRENCY_NOT_IN_SCHEMA can also appear here if the setting changes while a review waits.
| Code | HTTP | Why it happens | How to fix |
|---|---|---|---|
SAFEGUARD_BUSY | 503 | Safeguard was too busy to start this review within 120 seconds, so it was not done. | Retry the review in a minute with POST /api/v1/safeguard/reviews/{reviewId}/retry. If it keeps happening, contact PCIFIC support with the reviewId. |
SAFEGUARD_TIMED_OUT | 504 | The review took longer than 180 seconds. | Retry it with POST /api/v1/safeguard/reviews/{reviewId}/retry. If it keeps happening, contact PCIFIC support with the reviewId. |
SAFEGUARD_UNFINISHED | 422 | The AI did not send a complete review. Either it used 6 steps without finishing, or its answer was sent back 3 times for something wrong, such as a score outside 0 to 10 or a field that is not in your schema. | Retry it with POST /api/v1/safeguard/reviews/{reviewId}/retry. If it fails again, contact PCIFIC support with the reviewId. |
SAFEGUARD_MODEL_FAILED | 502 | Safeguard's AI service did not answer. This is a problem on PCIFIC's side. | Retry the review in a moment with POST /api/v1/safeguard/reviews/{reviewId}/retry. If it keeps happening, contact PCIFIC support. |
AI_PROVIDERS_BLOCKED | 503 | Every AI model that could answer is from a provider that PCIFIC has blocked for your business. | Contact PCIFIC support to allow another AI provider for your business, then retry the review. |
BYO_PROVIDER_ERROR | 502 | Your business uses its own AI model (Bring Your Own) and that model could not answer. The message identifies the failed AI connection. | Check with your provider that your key, its credits and the model are working, then retry the review. See Bring your own AI. |
SAFEGUARD_STOPPED | 500 | Work on the review stopped. There are three causes: the review stopped partway 3 times, the category could not be read back from your schema, or the review's saved details could not be read back. This is a problem on PCIFIC's side. The last cause can also answer a call to answers or retry straight away. | Retry the review once with POST /api/v1/safeguard/reviews/{reviewId}/retry. If it stops again, start a new review, and contact PCIFIC support with the reviewId. |
SAFEGUARD_NOT_SAVED | 500 | Only for a review Orbit started. The review of the finished listing could not be saved, so it cannot be read back, answered or retried. The listing itself is finished. It shows under pcific.safeguard in the Orbit listing. | Review the listing again with POST /api/v1/safeguard/reviews and {"orbitRunId": "<runId>"}. If it happens again, contact PCIFIC support with the runId. |
These come from POST /api/v1/images, which is Orbit's address. See the Orbit page for the photo rules, including iPhone photos (HEIC).
| Code | HTTP | Why it happens | How to fix |
|---|---|---|---|
ORBIT_TURNED_OFF | 403 | Photo uploads follow Orbit's switch, not Safeguard's. Orbit is switched off for this account. | Switch Orbit on in the dashboard, or send {"enabled": true} to PUT /api/v1/orbit/settings. |
NO_PHOTOS | 400 | The form has no photos in it. | Send each photo as a file field of a multipart/form-data body, for example with curl -F "photo=@front.jpg". |
NOT_A_FORM | 400 | The photos were not sent as a multipart/form-data form. | Send each photo as a file field, for example with curl -F. Do not turn the photos into base64 text. |
TOO_MANY_PHOTOS | 422 | One upload has more than 12 photos. | Send the 12 photos that show the item best. |
PHOTOS_REFUSED | 422 | No photos were saved because some could not be used. problems has one entry for each, like /images/2, with the reason. | Fix or leave out each photo listed, then send them all again. |
PHOTOS_TOO_LARGE | 413 | The photos together are over the limit for one upload, or the form was cut short. | Send fewer photos per upload (several uploads are fine), or send each photo as the phone took it rather than a RAW export. |
PHOTO_STORAGE_FAILED | 502 | The photos could not be stored. This is a problem on PCIFIC's side. | Send the photos again in a moment. If it keeps happening, contact PCIFIC support. |
The website checks body size first. Rust then checks retry headers, authentication, permission and the request fields. Network and key limits can refuse a request before later checks. Once a published category is identified, photo and listing-field problems are reported together, each with a next step. A 4xx refusal does not spend your Idempotency-Key: correct the request and send it again with the same key. A successful 202 replay returns the original reviewId ticket. Poll that reviewId for the current result, even if the ticket still says working. No second review is started.
Errors every address can give (a missing or revoked key, KEY_NOT_ALLOWED, INVALID_JSON, INVALID_FIELD, NOT_FOUND, RATE_LIMITED, TEST_DAILY_CAP_REACHED, OUT_OF_CREDITS and the rest) are on the Errors, limits and retries page. The same page explains the Idempotency-Key header, which POST /reviews, /answers and /retry all accept so that a repeated request is not done twice.
| What | Limit |
|---|---|
Request body | 256 KB |
listing | 50,000 characters as JSON |
sellerNotes | 5,000 characters |
Photos in a review | 12, each kept for 30 minutes after upload |
Each seller answer | 1,000 characters |
Rounds of seller answers | 3 for one review |
Time to start | A review that cannot start within 120 seconds fails with SAFEGUARD_BUSY |
Time to finish | 180 seconds, then SAFEGUARD_TIMED_OUT. Most reviews take 5 to 60 seconds |
How long a review is kept | 30 minutes after its last change |
Web searches | At most 2 for one review, and none for a review that writes a FAQ |
In a result | Up to 10 fixes, 6 questions, 6 sellerQuestions, 6 sources, 3 to 6 FAQ entries, a summary of up to 600 characters |
Rewrite | 1 to 5 field paths, none inside a list |
Settings | 50 banned items of up to 200 characters, instructions up to 2,000 characters, 2 to 5 verdicts with names up to 40 characters |
The request limit, the daily cap on test keys and your spend limits are on the Errors, limits and retries page and on API keys and permissions. Poll every 3 to 5 seconds, not in a tight loop. Starting a review, sending answers and retrying count as requests that start work.