API reference
Last updated v1
Watchpost is one HTTP endpoint. Send an intended purchase to POST /v1/verify with a connection token, and act on the approve / review / block verdict it returns. This is the same contract the MCP tools and the skill wrap. Your integration must submit accurate final details and honor the verdict before paying. Instructions alone are not an independent payment gate.
Base URL & auth
Base URL: https://api.watchpost.systems. Every request authenticates with a per-user connection token (create one in the app → Connections) sent as a bearer header:
Authorization: Bearer wp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxVerify a purchase
curl https://api.watchpost.systems/v1/verify \
-H "Authorization: Bearer $WATCHPOST_TOKEN" \
-H "content-type: application/json" \
-d '{
"agent": { "name": "my-agent" },
"merchant": { "domain": "amazon.com" },
"product": { "title": "USB-C cable, 1m" },
"payment": { "amountMinor": 1599, "currency": "USD" }
}'Request body (JSON):
{
"agent": { "name": string, "runtime"?: string },
"merchant": { "domain": string, "displayName"?: string },
"product": { "title": string, "url"?: string,
"description"?: string, "rawListingHtml"?: string },
"payment": { "amountMinor": integer, // MINOR units/cents: $15.99 -> 1599
"currency": string, // ISO 4217, e.g. "USD"
"isRecurring"?: boolean }
}amountMinor is in the currency's minor unit (cents), not its major unit. $15.99 is 1599, ¥6000 is 6000.
Verdict response (200):
{
"transactionId": "tx_…",
"decision": "approve" | "review" | "block",
"trustScore": 0-100,
"listingRiskScore": 0-100,
"listingScan": {
"status": "complete" | "mechanical_only" | "unavailable",
"reason": null | "insufficient_text" | "high_risk_detected" | "model_unavailable",
"mechanicalChecks": true,
"modelCheck": "completed" | "skipped" | "unavailable"
},
"category": string | null,
"ruleMatched": string | null,
"reasoning": [ { "pillar": "merchant"|"listing"|"rules",
"verdict": "pass"|"warn"|"fail",
"summary": string, "details"?: string[] } ],
// present only for "review":
"reviewExpectedWithinSeconds": 300,
// present only when verdict signing is configured (a compact JWS you can
// verify against /.well-known/jwks.json before acting):
"credential"?: string
}On approve, proceed. On block, do not pay and read the user the reasoning. On review, do not pay yet. Poll the status endpoint below until the user answers.
Read scan coverage
complete means mechanical and model analysis ran; it is not a guarantee of safety. mechanical_only means the model was skipped because there was too little text or a high-risk pattern was already found. unavailable means model analysis failed. Your integration should ask for review when analysis is unavailable, unless the purchase is already blocked. A low risk score must be read with this coverage. Older responses without coverage are unknown, not complete. The listing entry in reasoning also carries this coverage.
Wait for a review decision
When the verdict is review, the user decides in the dashboard. Push and email alerts require setup and may not arrive. Poll until resolved is true:
GET /v1/transactions/{transactionId}/status
->
{
"transactionId": "tx_…",
"decision": "review",
"outcome": "PENDING" | "USER_APPROVED" | "USER_BLOCKED" | "EXPIRED" | …,
"status": "pending" | "approved" | "blocked" | "expired",
"resolved": boolean,
"respondedAt": string | null
}Only complete the purchase when status is approved. A review that nobody answers expires after 24 hours and resolves as expired. Treat that (and blocked) as a decline; never pay on it.
After the payment attempt, report how it went so the record isn't left open:
POST /v1/transactions/{transactionId}/outcome
{ "outcome": "completed" | "failed" }Error shapes
Errors are JSON with an error code. The agent should relay a 402 to the user verbatim so the caller can act on it.
401 { "error": "invalid_token" } // bad / missing / revoked bearer token
402 { "error": "free_limit_reached", // free monthly cap hit
"limit": 10,
"upgradeUrl": "https://app.watchpost.systems/billing",
"resetsAt": "2026-08-01T00:00:00.000Z" }
429 { "error": "rate_limited", // + a Retry-After header (seconds)
"retryAfterSeconds": 30 }
409 { "error": "already_resolved" } // outcome/respond on a terminal tx
409 { "error": "not_reviewable" } // respond on a non-pending reviewPaid tiers never see the 402. A 409 on the outcome/respond endpoints means the transaction was already resolved (replaying the same outcome will not double-count it).
Protocol adapters
If your purchase is authorized through a structured protocol, post the native payload instead and Watchpost normalizes it: POST /v1/verify/acp (Agentic Commerce Protocol checkout session) and POST /v1/verify/ap2 (Google AP2 cart mandate). Both return the same verdict shape.