API reference
Base URL:
https://onetrial.dev. Auth: Authorization: Bearer ot_test_... (server-side only). Full schema: openapi.json.| method | path | summary | auth |
|---|---|---|---|
POST | /api/v1/visitor-token | Mint a visitor token (called by the browser snippet) | none |
POST | /api/v1/decisions | Score a signup and return a decision | Bearer key |
GET | /api/v1/decisions | List decisions | Bearer key |
GET | /api/v1/decisions/{id} | Get one decision | Bearer key |
POST | /api/v1/decisions/{id}/feedback | Label a decision as false or true positive | Bearer key |
POST | /api/v1/signals/card | Push a card fingerprint collected on your own Stripe account | Bearer key |
GET | /api/c/{challengeId} | Visitor-safe challenge status | none |
POST | /api/v1/challenges/{id}/complete | Headless: report the outcome of your own verification UI | Bearer key |
GET | /api/v1/allowlist | List allowlist entries | Bearer key |
POST | /api/v1/allowlist | Add a allowlist entry | Bearer key |
DELETE | /api/v1/allowlist | Remove a allowlist entry | Bearer key |
GET | /api/v1/blocklist | List blocklist entries | Bearer key |
POST | /api/v1/blocklist | Add a blocklist entry | Bearer key |
DELETE | /api/v1/blocklist | Remove a blocklist entry | Bearer key |
GET | /api/v1/webhooks | List endpoints (add ?deliveries=<id> for its delivery log) | Bearer key |
POST | /api/v1/webhooks | Register an endpoint; the signing secret is returned once | Bearer key |
DELETE | /api/v1/webhooks | Remove an endpoint | Bearer key |
GET | /api/v1/integration/status | Machine-readable integration checklist | Bearer key |
GET | /api/v1/settings | Workspace settings | Bearer key |
PATCH | /api/v1/settings | Update thresholds, shadow mode, stack, plan price | Bearer key |
POST | /api/v1/keys | Create another API key | Bearer key |
POST | /api/device/code | Start device authorization (CLI/MCP) | none |
POST | /api/device/token | Poll for the approved API key | none |
POST /api/v1/decisions
Request:
json{ "visitorToken": "eyJ...", "email": "jane@example.com", "ip": "203.0.113.7", "cardFingerprint": "optional", "userId": "optional", "metadata": {} }
Response:
json{ "decisionId": "uuid", "decision": "challenge", "score": 58, "reasons": [{ "code": "D_DEVICE_REPEAT", "message": "Device previously used for a consumed trial", "weight": 20.4 }], "challengeOptions": ["require_card"], "challengeId": "uuid", "challengeUrl": "https://onetrial.dev/c/uuid", "usage": { "used": 12, "limit": 500, "warn": false } }
In shadow mode the same call returns
"decision": "allow", "shadow": true, "wouldBe": "challenge".Coupon and promo redemptions
The same endpoint answers whether a promo code redemption is a repeat. Send
event: "promo" with the code:json{ "event": "promo", "promoCode": "WELCOME50", "email": "jane@example.com", "ip": "203.0.113.7", "visitorToken": "eyJ...", "userId": "usr_8213" }
event defaults to signup, so existing calls are unchanged. A promo redemption is recorded as its own kind of
event and never counts as a consumed trial: without that, a paying customer who redeems a coupon would gain a
second trial on their own card and device, and later decisions about them would read it as a repeat.The code is never treated as an identity, because a public campaign code is typed by thousands of unrelated
people. It counts as evidence only when an account already linked to this person redeemed the same code.
Errors
Every 4xx/5xx body is
{ "error": { "code", "message", "fix" } }. fix is the exact corrective step; agents should act on it.| status | code | fix |
|---|---|---|
| 401 | missing_api_key / invalid_api_key | Send Authorization: Bearer ot_test_... from the dashboard |
| 400 | validation_error | The message names the field; correct it |
| 400 | invalid_email / invalid_ip | Send the raw email and the client IP (from x-forwarded-for) |
| 429 | plan_limit_reached | Free plan cap hit this month; upgrade under Billing |
| 409 | challenge_resolved | The challenge already passed or failed; request a new decision |
| 409 | stripe_not_connected | Connect Stripe or push fingerprints to POST /api/v1/signals/card |