Getting started with the API
Request access, make a check, and show the result.
Access is by request.
The API is by request only for now, so there is no plan to pick and no price list. Every check you send draws credits, and the depth you ask for sets how many: 5 at JUST_THE_FACTS, 250 at DEEP_DIVE.
Ask for access in the next step.
Your key.
Send it as Authorization: Bearer on every request. Keep it on your server. Never put it in a browser or a mobile app.
Your first request.
Send the same request in curl, Python, JavaScript or TypeScript. Python uses requests. JavaScript uses Node 18 or later.
job=$(curl -X POST https://api.backgrounder.com/api/analyze-content \
-H "Authorization: Bearer $CARMEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":{"text":"Your parcel is held. Pay the release fee at parcel-redelivery.info"},"options":{"createCase":true}}')import os
import requests
r = requests.post(
"https://api.backgrounder.com/api/analyze-content",
headers={"Authorization": f"Bearer {os.environ['CARMEN_API_KEY']}"},
json={
"content": {"text": "Your parcel is held. Pay the release fee at parcel-redelivery.info"},
"options": {"createCase": True},
},
)
r.raise_for_status()
job = r.json()const res = await fetch("https://api.backgrounder.com/api/analyze-content", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CARMEN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
content: { text: "Your parcel is held. Pay the release fee at parcel-redelivery.info" },
options: { createCase: true },
}),
});
if (!res.ok) throw new Error(await res.text());
const job = await res.json();interface CarmenResponse {
job_id: string;
status: "pending" | "completed";
mode: "sync" | "async";
links?: { status: string; result: string };
result?: {
verdict: "clean" | "unknown_fresh" | "suspicious" | "malicious";
headline: string;
recommended_action: string;
evidence_trail?: { source: string; signal: string; weight: number; observation: string }[];
};
}
const res = await fetch("https://api.backgrounder.com/api/analyze-content", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CARMEN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
content: { text: "Your parcel is held. Pay the release fee at parcel-redelivery.info" },
options: { createCase: true },
}),
});
if (!res.ok) throw new Error(await res.text());
const job: CarmenResponse = await res.json();Read the response.
A bare link, a person or a deepfake check returns 200 with mode "sync" and the result inline.
A message or an email returns 202 with mode "async", status "pending", links.status and links.result. Poll links.result until status is "completed".
The examples below show the two response shapes. Use the URLs returned in links.
200 · Sync
{
"job_id": "example-sync",
"status": "completed",
"mode": "sync",
"result": {
"verdict": "clean",
"headline": "Low risk. The link goes to the expected website.",
"recommended_action": "Nothing to do."
}
}202 · Async
{
"job_id": "example-async",
"status": "pending",
"mode": "async",
"links": {
"status": "<status URL returned by the API>",
"result": "<result URL returned by the API>"
}
}Continue from your first request. The curl polling example uses jq.
result_url=$(printf '%s' "$job" | jq -r '.links.result'); status=$(printf '%s' "$job" | jq -r '.status')
while [ "$status" != "completed" ]; do
sleep 2; job=$(curl --fail --silent --show-error -H "Authorization: Bearer $CARMEN_API_KEY" "$result_url"); status=$(printf '%s' "$job" | jq -r '.status')
doneimport time
result_url = job.get("links", {}).get("result")
while job["status"] != "completed":
time.sleep(2); r = requests.get(result_url, headers={"Authorization": f"Bearer {os.environ['CARMEN_API_KEY']}"}); r.raise_for_status(); job = r.json()let completed = job; const resultUrl = job.links?.result;
while (completed.status !== "completed") {
await new Promise(resolve => setTimeout(resolve, 2000)); const poll = await fetch(resultUrl, { headers: { Authorization: `Bearer ${process.env.CARMEN_API_KEY}` } }); if (!poll.ok) throw new Error(await poll.text()); completed = await poll.json();
}let completed: CarmenResponse = job; const resultUrl = job.links?.result;
while (completed.status !== "completed" && resultUrl) {
await new Promise(resolve => setTimeout(resolve, 2000)); const poll = await fetch(resultUrl, { headers: { Authorization: `Bearer ${process.env.CARMEN_API_KEY}` } }); if (!poll.ok) throw new Error(await poll.text()); completed = await poll.json() as CarmenResponse;
}Show it to your user.
Map the verdict: clean is Low risk, unknown_fresh and suspicious are Medium risk, malicious is High risk. Show the headline and the recommended_action as they are, because they are written for a person. evidence_trail has up to six reasons with a source and a weight if you want to show why.
Limits and errors.
401: the key is wrong or missing. 429: out of credits or over your rate. 400: the body did not validate, and the error message says why. The same content sent again within an hour returns the same verdict.