Qorami.AI

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

FieldTypeDescription
recipientstringEmail recipient.
subjectstringSubject.
bodystringEmail body.
policyProfilestringgeneral, sales, support or legal-finance.
idempotencyKeystringOptional: replays the same result without re-charging.

Response — the agent contract

Obey nextAction.type:

nextAction.typeWhat the agent does
sendSend the email.
request_human_confirmationPause: a human must approve (notified by email + client area).
do_not_sendDo 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:

HTTPError codeDescription & 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/.

For agent fleets

Running several agents under one workspace? These make each one observable and independently controllable.

More endpoints

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.