API Request access
Overview Reference
API reference

One call, one contract.

Everything on this page works against the production endpoint today. Keys are issued with access. The versioned host and header authentication described at the end are the next change, and the current contract keeps working after it ships.

Authentication

Your organisation key identifies the account and carries the plan, the limits and any custom policies you uploaded. Send it in the request body. Keep it server side: a key in a browser or a mobile binary is a key you have published.

One key per environment. Rotating a key takes effect immediately, so rotate staging and production separately.

Check a draft

POST https://verbapulse.com/analyzecurl
curl -X POST https://verbapulse.com/analyze \
  -H "Content-Type: application/json" \
  -d '{
    "text": "As agreed, we will match any competitor price for the next three years.",
    "org_api_key": "<your key>",
    "platform": "support-agent"
  }'

Request fields

FieldTypeNotes
textstringRequired. The draft to check, up to 50,000 characters.
org_api_keystringRequired. Your organisation key.
platformstringFree label for your own reporting, for example support-agent.
user_idstringYour user or agent identifier, if you want per-user reporting and controls.
departmentstringGroups the event in department-level reporting.
recipientsstring[]Recipient addresses, when the draft is an outbound message. Enables NDA checks.
contextstringemail by default. Use ai_tool for text a person is pasting into an assistant.

Response

200 OKreal output
{
  "status": "ok",
  "session_id": "dd1597af-fca5-4c11-9176-ec3a4e0755dc",
  "nda_recipients": [],
  "segments": [
    {
      "text": "we will match any competitor price for the next three years",
      "sentence": "As agreed, we will match any competitor price for the next three years.",
      "start": 11,
      "end": 70,
      "risk_level": "medium",
      "risk_type": "compliance",
      "explanation": "Firm long-term pricing commitment, confirm approval and contractual alignment",
      "suggested_sentence": "we can discuss matching competitor pricing as part of our agreement",
      "fix_type": "sentence"
    }
  ]
}
FieldNotes
statusok when the draft was checked. unavailable when it was not.
segmentsFindings. Empty array with ok means the draft is clean.
start, endCharacter offsets into the text you sent, for highlighting or in-place replacement.
risk_levellow, medium, high, critical.
risk_typeThe category, for example compliance, legal, financial_crime, confidentiality.
fix_typephrase when the flagged span can be replaced, sentence when the sentence needs rewriting.
session_idCorrelates later events, for example that the writer accepted the suggestion.
nda_recipientsRecipients without an NDA on file, when you send recipients.
detailPresent on some unavailable responses. A sentence you can show the writer, for example that an allowance is spent. Its presence means retrying will not help.

The one integration rule

Treat anything other than 200 with "status": "ok" as not checked, and say so in your product. A refusal carries no verdict, and an empty segments array in a non-200 response is not a clean draft.

We shipped this bug ourselves. Our Outlook add-in read the body without checking the status code, so a rate-limited response rendered as "no risks detected". It is the most dangerous failure this product can have, and the same trap is waiting in any integration that parses the body first.

if r.status_code != 200 or r.json().get("status") != "ok":
    # show "not checked" and let the human decide; never fall through to "clean"
    return Unchecked(reason=r.status_code)

Status and error codes

CodeMeaningWhat to do
200 + okChecked.Use segments.
200 + unavailableThe check did not complete. A detail string says why when the reason is not transient.Show "not checked". Retry once after a short pause, unless detail is present.
200 + deactivatedThe key or the user is not active.Stop sending. Check the account.
413Draft over 50,000 characters.Split the text and check the parts.
429Rate limit exceeded.Back off and retry. Ask us for a higher limit.
5xxServer error.Show "not checked". Retry with backoff.

Limits and behaviour

Rate limit600 requests per minute per key. Higher limits on request.
Latency2 to 5 seconds for a short draft. Call the check in parallel with your own generation and hold the reply only on a finding.
Repeat textAn identical draft from the same account within a few hours may return a cached result. Vary nothing and you will get the same answer quickly.
ConcurrencyNo hard cap on your side. Sustained load beyond your plan's capacity queues, so size the commitment to your peak.
RetriesSafe. A check has no side effect other than the event record.
Evaluation keys5,000 checks in total and 500 in any rolling 24 hours. Past either ceiling the response is unavailable with a detail.

Examples

Pythonhttpx
import httpx

def check(text: str) -> dict:
    r = httpx.post(
        "https://verbapulse.com/analyze",
        json={"text": text, "org_api_key": KEY, "platform": "support-agent"},
        timeout=30,
    )
    if r.status_code != 200:
        return {"checked": False, "reason": r.status_code}
    d = r.json()
    if d.get("status") != "ok":
        return {"checked": False, "reason": d.get("status")}
    return {"checked": True, "findings": d["segments"]}
Nodefetch
async function check(text) {
  const r = await fetch("https://verbapulse.com/analyze", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ text, org_api_key: KEY, platform: "support-agent" }),
  });
  if (!r.ok) return { checked: false, reason: r.status };
  const d = await r.json();
  if (d.status !== "ok") return { checked: false, reason: d.status };
  return { checked: true, findings: d.segments };
}

Data handling

What you may do with the findings we return is set out in section 6 of the terms. In short: use them to check your own organization's communications, and do not use them to train or evaluate a model that does the same job.

Next change

A versioned host with header authentication, POST https://api.verbapulse.com/v1/analyze with Authorization: Bearer, plus a usage endpoint. The body key described above keeps working after it ships. If you are integrating now, put the base URL and the auth method behind one constant and the migration is a two-line change.