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.
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.
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"
}'
| Field | Type | Notes |
|---|---|---|
text | string | Required. The draft to check, up to 50,000 characters. |
org_api_key | string | Required. Your organisation key. |
platform | string | Free label for your own reporting, for example support-agent. |
user_id | string | Your user or agent identifier, if you want per-user reporting and controls. |
department | string | Groups the event in department-level reporting. |
recipients | string[] | Recipient addresses, when the draft is an outbound message. Enables NDA checks. |
context | string | email by default. Use ai_tool for text a person is pasting into an assistant. |
{
"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"
}
]
}
| Field | Notes |
|---|---|
status | ok when the draft was checked. unavailable when it was not. |
segments | Findings. Empty array with ok means the draft is clean. |
start, end | Character offsets into the text you sent, for highlighting or in-place replacement. |
risk_level | low, medium, high, critical. |
risk_type | The category, for example compliance, legal, financial_crime, confidentiality. |
fix_type | phrase when the flagged span can be replaced, sentence when the sentence needs rewriting. |
session_id | Correlates later events, for example that the writer accepted the suggestion. |
nda_recipients | Recipients without an NDA on file, when you send recipients. |
detail | Present 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. |
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)
| Code | Meaning | What to do |
|---|---|---|
200 + ok | Checked. | Use segments. |
200 + unavailable | The 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 + deactivated | The key or the user is not active. | Stop sending. Check the account. |
413 | Draft over 50,000 characters. | Split the text and check the parts. |
429 | Rate limit exceeded. | Back off and retry. Ask us for a higher limit. |
5xx | Server error. | Show "not checked". Retry with backoff. |
| Rate limit | 600 requests per minute per key. Higher limits on request. |
|---|---|
| Latency | 2 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 text | An 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. |
| Concurrency | No hard cap on your side. Sustained load beyond your plan's capacity queues, so size the commitment to your peak. |
| Retries | Safe. A check has no side effect other than the event record. |
| Evaluation keys | 5,000 checks in total and 500 in any rolling 24 hours. Past either ceiling the response is unavailable with a detail. |
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"]}
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 };
}
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.
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.