API reference
One POST per submission returns allow, hold or block, a score and the reasons. Every endpoint, field and error.
The API checks any submission from any platform: sign-up forms, checkouts, support widgets, comment systems or mobile apps. It’s the same engine the WordPress plugin uses.
Base address: https://api.cayve.co
Authentication
Send a key as a bearer token on every request:
Authorization: Bearer cv_live_…
Create keys on a site’s page in the dashboard. A key belongs to one site.
- Live keys (
cv_live_…) are counted and act. - Test keys (
cv_test_…) give fixed answers and aren’t counted: a submission withcayve-test-blockin any field is blocked, one withcayve-test-holdis held, and anything else is allowed.
Keep keys on your server. Never put a key in browser or app code.
Check a submission
POST /v1/check
{
"submission_id": "contact-8f2c91",
"type": "contact",
"content": {
"name": "Dana Ruiz",
"email": "dana@example.com",
"message": "Hi, could you quote 40 cases for our two shops?"
},
"submitter": {
"ip": "203.0.113.9",
"user_agent": "Mozilla/5.0 …"
},
"context": {
"page_url": "https://example.com/contact",
"form_name": "Contact"
}
}
Request fields
| Field | Required | What it is |
|---|---|---|
submission_id |
Yes | Your id for this submission, up to 128 characters. The same id sent again returns the original decision and is counted once. |
type |
No | contact (default), comment, review, registration, signup, order or other. |
content |
Yes | message, name, email, url, subject, phone, and fields (up to 50 other fields as label → value). Send what you have. |
submitter |
No | ip, user_agent, accept_language and referrer of the person who submitted. The IP matters most. |
client |
No | Evidence from your page: honeypot (a hidden field was filled), elapsed_ms (time from page load to submit), javascript (your page script ran), automation (markers like webdriver). |
context |
No | site_url, page_url, form_id, form_name, integration and site_language. Shown in the dashboard. |
Response
{
"id": "chk_…",
"verdict": "allow",
"score": 4,
"category": "legit",
"reasons": [],
"review": "none",
"mode": "enforce",
"engine": "2026.11.2",
"duplicate": false,
"test": false
}
| Field | What it means |
|---|---|
verdict |
allow, hold or block. What to do with the submission. |
score |
0 (clearly real) to 100 (clearly spam). |
category |
legit, spam, scam, seo_outreach, sales_pitch or job_seeker. |
reasons |
Each has a code, a plain text, a technical text and a weight. Show them to your team, never to the person who submitted. |
mode |
shadow means record only: treat the submission as allowed whatever the verdict says. |
duplicate |
true when this submission_id was checked before. |
test |
true for answers given to a test key. |
Acting on the answer
- allow: deliver as normal.
- hold: save it, but keep it out of the inbox until someone on your team reviews it.
- block: don’t deliver it. Keep it for a while in case you need it back.
Always show the person who submitted your normal success message, whatever the verdict. If Cayve doesn’t answer within a second or two, let the submission through.
Tell Cayve what it was
POST /v1/feedback
{ "detection_id": "chk_…", "label": "spam" }
label is spam or legit. The sender and the message are added to the site’s own block or allow list straight away. Send this whenever your team releases a held submission or confirms spam.
Other endpoints
| Endpoint | What it returns |
|---|---|
GET /v1/verify |
Whether the key is valid, and the account and site it belongs to. |
GET /v1/usage |
This month’s checks and the plan’s allowance. |
GET /v1/detections?limit=50 |
Recent decisions for the key’s site (up to 100). |
GET /v1/detections/:id |
One decision. |
Errors
Errors come back with the matching HTTP status and this body:
{ "error": { "code": "validation_failed", "message": "…", "details": [{ "path": "content", "message": "…" }] } }
| Code | Status | Meaning |
|---|---|---|
unauthorized |
401 | The key is missing, wrong or revoked. |
forbidden |
403 | The account isn’t active. |
not_found |
404 | No such decision for this site. |
validation_failed |
422 (400 if the body can’t be read) | A field is missing or the wrong shape; details says which. |
payload_too_large |
413 | The request is too big. |
rate_limited |
429 | Too many requests for this key. Wait and try again; a Retry-After header may say how long. |
internal, unavailable |
500, 503 | Something went wrong on our side. Let the submission through. |
Running out of your monthly allowance is never an error. The fast checks keep answering.
See API examples for code in Node.js, PHP and Python.
Something unclear or missing? Email hello@cayve.co.