Gate API
Put the gate in front of anything you send out.
Two calls. protect swaps client data for placeholders before your text goes to an AI, a vendor or a log. restore puts the real values back into whatever comes back. The same industry rules, names settings and client list as your firm's desk apply.
Keys
An owner creates keys in Owner view, API tab. A key is shown once; we keep only a hash of it. Send it on every request:
Authorization: Bearer sdk_…
Keys act for the firm that made them. Revoke a key and it stops working immediately. Keep keys on your server; never put one in a web page or a mobile app.
POST /api/v1/protect
curl https://desk.sophiaxt.com/api/v1/protect \
-H "Authorization: Bearer $SAFE_DESK_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Client Dana Reyes, SSN 123-45-6789, needs a letter."}'
Response:
{
"outcome": "alerted",
"protected_text": "Client [NAME_1], SSN [SSN_1], needs a letter.",
"findings": [
{ "rule": "ssn", "label": "Social Security number", "action": "alert" },
{ "rule": "name", "label": "Person's name (detected)", "action": "redact" }
],
"vault": "v1.…"
}
| Field | Meaning |
|---|---|
outcome | clean, redacted, alerted (the owner was notified) or blocked. |
protected_text | The text to send onward. null when blocked: passwords, keys and privileged material are never returned. |
findings | What was caught, by rule. Never the values. |
vault | The swap table, sealed with AES-256-GCM and bound to your firm. You can't read it and neither can anyone you pass it to. Send it back with the next protect call in the same conversation so the same value keeps the same placeholder, and with restore. |
POST /api/v1/restore
curl https://desk.sophiaxt.com/api/v1/restore \
-H "Authorization: Bearer $SAFE_DESK_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Letter for [NAME_1] ([SSN_1]).", "vault": "v1.…"}'
{ "text": "Letter for Dana Reyes (123-45-6789)." }
A full round trip in JavaScript
const gate = (path, body) => fetch(`https://desk.sophiaxt.com/api/v1/${path}`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SAFE_DESK_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
}).then((r) => r.json());
const p = await gate("protect", { text: userText });
if (p.outcome === "blocked") throw new Error("Stopped by Safe-Desk: " + p.findings.map((f) => f.label).join(", "));
const answer = await yourModel(p.protected_text); // your AI never sees the values
const { text } = await gate("restore", { text: answer, vault: p.vault });
Errors and limits
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request, bad_vault | Missing fields, or a vault your firm didn't issue or that was altered. |
| 401 | unauthorized | Missing, wrong or revoked key. |
| 402 | The firm's subscription isn't active. | |
| 413 | too_large | Text over 100,000 characters. |
| 429 | rate_limited | Over 600 requests a minute for one key. Retry after the Retry-After seconds. |
Metering
Text through protect counts toward your firm's included AI allowance at about four characters a token, the same pool as the desk: 2 million tokens per staff member a month. Beyond that, $2 per million tokens. restore is not metered. Usage is on the Billing tab.
What the API stores
Nothing you send. The text, the protected text and the values are never written to our database or logs. Your owner's log records that a call was made, by which key's account, and which rules fired. All traffic is HTTPS.