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 with cayve-test-block in any field is blocked, one with cayve-test-hold is 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.