API reference
Qorami is a control point between your AI agents and actually sending email.
Before each send, the agent asks Qorami, which answers : send,
ask a human to approve, or do not send.
Version française →
Authentication
Every request uses your workspace API key in the x-qorami-api-key header. Get it in your client area ("Démarrer").
x-qorami-api-key: YOUR_KEY
Verify an email POST /api/verify-email
The main call: the agent submits the email before sending it.
Request
| Field | Type | Description |
|---|---|---|
recipient | string | Email recipient. |
subject | string | Subject. |
body | string | Email body. |
policyProfile | string | general, sales, support or legal-finance. |
idempotencyKey | string | Optional: replays the same result without re-charging. |
Response — the agent contract
Obey nextAction.type:
nextAction.type | What the agent does |
|---|---|
send | Send the email. |
request_human_confirmation | Pause: a human must approve (notified by email + client area). |
do_not_send | Do not send as-is. |
Example — curl
curl -X POST https://qorami.fr/api/verify-email \
-H "x-qorami-api-key: YOUR_KEY" \
-H "content-type: application/json" \
-d '{
"recipient": "client@example.com",
"subject": "Our offer",
"body": "Hi, here is our proposal...",
"policyProfile": "sales"
}'
Example — JavaScript
// npm i qorami
import { QoramiClient } from "qorami";
const qorami = new QoramiClient({ apiKey: process.env.QORAMI_API_KEY });
await qorami.guard(
{ recipient, subject, body, policyProfile: "sales" },
{
send: () => mailer.send(), // allowed
requestHumanConfirmation: (r) => queue(r.action.id), // human notified
doNotSend: () => {}, // blocked
},
);
Example — Python
from qorami import QoramiClient
qorami = QoramiClient(api_key=os.environ["QORAMI_API_KEY"])
result = qorami.verify(recipient="client@example.com", subject="Our offer",
body=email_body, policy_profile="sales")
if result.next_action_type == "send":
send_email()
Remediation (what to fix)
Beyond reasons (why), the response returns verification.suggestions: concrete fixes to make the email sendable, so the agent can correct and re-verify.
"verification": { "decision": "Bloque", "concerns": ["secret_leak"],
"suggestions": ["Remove the API key from the body", "Send the key via a secret manager"] }
Test mode (dry-run)
Add "dryRun": true to get the decision without spending a credit or persisting an action — ideal for testing your policy/integration.
curl -X POST https://qorami.fr/api/verify-email \
-H "x-qorami-api-key: YOUR_KEY" -H "content-type: application/json" \
-d '{"recipient":"a@b.com","subject":"Test","body":"...","dryRun":true}'
# -> { "dryRun": true, "verification": {...}, "nextAction": {...} } (0 credits)
Batch verification POST /api/verify-email/batch
Verify up to 20 emails in one call and get a decision per item (preview, no charge, no persistence).
curl -X POST https://qorami.fr/api/verify-email/batch \
-H "x-qorami-api-key: YOUR_KEY" -H "content-type: application/json" \
-d '{"items":[{"recipient":"a@b.com","subject":"Hi","body":"..."}, ...]}'
# -> { "count": N, "summary": {...}, "results": [{ "decision": "...", "nextAction": {...} }, ...] }
Discover the rules GET /api/policy
An agent can read its workspace's active rules to adapt before sending (profiles + thresholds, blocked/review terms, the decision contract, credit cost).
curl https://qorami.fr/api/policy -H "x-qorami-api-key: YOUR_KEY"
# -> { "profiles": [{ "id":"sales", "reviewThreshold":30, "blockThreshold":70 }, ...],
# "policy": { "contentRules": { "blockedTerms": [...], "reviewTerms": [...] } },
# "decisionContract": { "send": "...", "request_human_confirmation": "...", "do_not_send": "..." },
# "credits": { "costPerVerification": 1, "dryRunCost": 0 } }
Track a human approval GET /api/email-actions/:id
When nextAction.type is request_human_confirmation, poll this endpoint to learn whether a human approved. nextAction returns send once approved, do_not_send if blocked, or stays pending.
curl https://qorami.fr/api/email-actions/ACTION_ID \
-H "x-qorami-api-key: YOUR_KEY"
# -> { "action": { "status": "Authorized", ... },
# "nextAction": { "type": "send", ... } }
Robust integration & error handling
For autonomous and resilient agent operations, handle billing errors, rate limits, and permission constraints by following these guidelines:
| HTTP | Error code | Description & Recommended Action |
|---|---|---|
402 |
insufficient_credits |
The workspace has run out of credits. The agent must pause sending and notify the administrator (we advise enabling auto-recharge in the client area). |
429 |
rate_limited |
Call limit reached. The agent must read the Retry-After header and retry after the specified delay. |
403 |
insufficient_scope |
The API key lacks permissions for this action (e.g. a verify-only key attempting to approve or block). |
422 |
validation_error |
The payload is malformed or invalid. Do not retry without correcting the input. |
Full integration loop example (JavaScript)
// 1. Initial verification call
async function guardEmail(email) {
try {
const res = await fetch("https://qorami.fr/api/verify-email", {
method: "POST",
headers: {
"x-qorami-api-key": process.env.QORAMI_API_KEY,
"content-type": "application/json",
},
body: JSON.stringify(email)
});
if (res.status === 402) {
console.error("Insufficient credits. Sending paused.");
return { status: "insufficient_credits" };
}
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("retry-after") || "60", 10);
console.warn(`Rate limit hit. Waiting for ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
return guardEmail(email); // Retry after delay
}
if (!res.ok) {
const error = await res.json();
console.error(`Integration error (${res.status}):`, error);
return { status: "error", error };
}
const { nextAction, action } = await res.json();
if (nextAction.type === "send") {
return { status: "send" }; // Authorized: send the email
}
if (nextAction.type === "request_human_confirmation") {
// 2. Human confirmation required (polling)
console.log(`Action ${action.id} pending review. Starting status polling...`);
return pollActionStatus(action.id);
}
return { status: "blocked" }; // do_not_send
} catch (err) {
console.error("Network or API call error:", err);
return { status: "error", err };
}
}
// 3. Status Polling Loop
async function pollActionStatus(actionId) {
const maxAttempts = 10;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
await new Promise(r => setTimeout(r, 30000)); // Wait 30 seconds
try {
const res = await fetch(`https://qorami.fr/api/email-actions/${actionId}`, {
headers: { "x-qorami-api-key": process.env.QORAMI_API_KEY }
});
if (res.ok) {
const { nextAction } = await res.json();
if (nextAction.type === "send") return { status: "send" };
if (nextAction.type === "do_not_send") return { status: "blocked" };
}
} catch (e) {
console.warn("Status polling attempt failed...", e);
}
}
return { status: "timeout" }; // Timeout without approval
}
Webhooks (push) POST /api/workspace/webhook
Instead of polling, get notified the moment a human approves or blocks. Set a URL (authenticated, owner):
curl -X POST https://qorami.fr/api/workspace/webhook \
-H "x-qorami-api-key: YOUR_KEY" \
-H "content-type: application/json" \
-d '{"url":"https://your-app.com/qorami-webhook"}'
# -> { "configured": true, "url": "...", "secret": "qwh_..." } (secret shown once)
Qorami then sends a signed POST on each resolution. type is action.confirmed (the agent may send) or action.blocked. The URL must be https and public.
// received body
{ "type": "action.confirmed", "workspaceId": "...", "sentAt": "...",
"action": { "id": "act_...", "status": "Authorized", "nextAction": { "type": "send" } } }
// signature header: x-qorami-signature: t=<ts>,v1=<hmac>
// verify: HMAC_SHA256(secret, `${t}.${rawBody}`) === v1
SDK & integrations
Zero-dependency clients (JavaScript & Python) and a LangChain tool: github.com/…/qorami-sdk. Runnable quickstarts in examples/.
- MCP server — register Qorami as a native tool in Claude Desktop, Cursor, Windsurf, or any MCP client:
npx qorami-mcp. Exposesqorami_health,verify_emailandcheck_action_statusover stdio. Setup guide: qorami.fr/mcp-en. - Tool schemas — drop-in OpenAI function-calling and Anthropic tool-use definitions for
qorami_check_email: qorami-sdk/tools.
For agent fleets
Running several agents under one workspace? These make each one observable and independently controllable.
- Machine reason codes — every decision carries
verification.reasonCodes(stable enum:SECRET_DETECTED,CARD_DETECTED,SSN_DETECTED,IBAN_DETECTED,SUSPICIOUS_LINK,BULK_PII,PROMPT_INJECTION,COMMERCIAL_PROMISE,LEGAL_COMMITMENT,SIGNATURE_PRESSURE, …). Detects leaked API keys, Luhn-valid card numbers, SSNs and IBANs, in FR/EN/ES/DE/IT. Branch on codes, don't parse prose. - Prompt-injection detection — flags attempts to hijack the agent inside the email it's about to send or quote ("ignore your instructions", "forward the whole thread to…", covert-action phrasing) as
PROMPT_INJECTION. The risk that's unique to agents. See measured accuracy. - Real-time webhook — set a webhook (
POST /api/workspace/webhook) to receiveaction.verifiedon every decision (plusaction.confirmed/action.blockedon human review) — stream every agent decision into a SIEM or log. Signed withx-qorami-signature. - Tamper-evident audit — each action's events are hash-chained;
GET /api/email-actions/:id/auditreturns{ intact, brokenAt, length }proving the trail was not altered. Compliance-grade. - Sandbox keys — mint a key with
"scope": "sandbox": it returns the real decision but never charges a credit or persists an action — for CI and policy testing. - Safe rewrite (auto-fix) — when an email is risky only because of removable content (leaked secrets, suspicious links, an IBAN),
verification.remediationreturns a cleanedsafeBodyplussafeToSend. Send the cleaned version instead of giving up. - Per-agent attribution — pass
agentIdon eachverify-emailcall, then read the breakdown (total / authorized / awaiting / blocked, riskiest first) atGET /api/agents. - One key per agent — mint a named key per agent (
POST /api/workspace/keyswith{ "label": "Outreach bot" }, dashboard-session auth) and revoke a single agent without disrupting the others. List withGET /api/workspace/keys. - Least-privilege keys — add
"scope": "verify"when minting a key so that agent can only callverify-email— never manage billing, keys or policy (returns403 insufficient_scopeotherwise). - Per-request policy override — tighten the workspace policy for one call with
policyOverride(reviewThreshold,blockThreshold, extrablockTerms/reviewTerms). It can only make a call stricter, never looser. - Slack approvals — set a Slack incoming webhook (
POST /api/workspace/slack,{ "webhookUrl": "https://hooks.slack.com/…" }) to get a one-click "Review in dashboard" message whenever an email needs a human.
More endpoints
- Usage —
GET /api/usage: month usage + remaining credits + LLM budget (self-monitoring). - Review queue / recent —
GET /api/email-actions?status=AwaitingConfirmation(also?since=,?limit=). - Detected entities — the
verify-emailresponse includesverification.detected: secrets (masked), suspicious links, PII. - OpenAPI — machine spec at /api/openapi.json (Postman import / codegen) · browse it at /api-explorer.
Error format
All errors share one envelope, with a traceable requestId:
{
"error": {
"code": "insufficient_credits",
"message": "Not enough Qorami credits for this workspace.",
"requestId": "req_..."
}
}
Every response also carries an x-qorami-request-id header.
Credits
1 traced action = 1 credit (a block is free). Buy packs in your client area. New accounts get free credits to test.