Developer documentation. Three ways in, one credential, and one idea running through all of them: nothing identifying reaches a model.
Base URL
https://secureai.one/v1
The Worker's own hostname, https://secure-ai-worker.secureai-one.workers.dev, answers the same routes and always will. The address above is proxied to it so that examples read as one domain.
Quickstart
Sixty seconds, assuming you have a key from Settings → Developer.
Take the people out of a piece of text:
curl https://secureai.one/v1/redact \
-H "Authorization: Bearer sai_..." \
-H "Content-Type: application/json" \
-d '{"text": "Email Sara Whitfield at sara@example.com"}'
{ "object": "restoration", "text": "I emailed Sara Whitfield.", "restored": 1 }
That is the whole product. Everything below is detail.
If you would rather not hold the map or make two calls, the proxy does all three steps in one request using your own model key — see `/v1/proxy/chat/completions`.
Which one you want
You want
Use
Whose model
To keep your existing AI provider, and change one line
/v1/proxy/chat/completions
Yours
To control exactly what goes where
/v1/redact + /v1/restore
Yours
To not have a provider at all
/v1/chat/completions
Ours
The first is what most people should use. The second is the same work with the seams exposed. The third is for people who would rather buy the whole thing.
Authentication
Every endpoint takes an API key, created in the app under Settings → Developer.
Authorization: Bearer sai_...
The key is shown once when you create it. What we store is its SHA-256, so it cannot be shown again or recovered — if you lose it, revoke it and make another. A key acts for the account that created it, carries that account's plan, and can be revoked on its own without touching the others.
A request with no key, or with something that is not a key, gets 401. That includes a valid sign-in token: tokens are for the apps, this door takes keys.
API access
A key says who is calling. A subscription says whether they may.
/v1/redact, /v1/restore and the proxy need API access, bought separately from the app plans — it is a different product for different people. A Pro subscriber has no API access unless they bought it, and an account on the free plan that bought only this has it.
Without it, those endpoints answer 402 with code api_access_required.
Access is a switch rather than a stamp on the key, so a lapsed subscription leaves your keys in place and stops them working, and renewing turns them all back on with nothing to re-issue.
What the subscription includes
50,000 requests a month. Past that, $0.001 each, taken from a balance you top up in advance.
Every call to /v1/redact, /v1/restore and the proxy counts as one request — restore included, because otherwise the two-call integration would be the cheap way to use the one-call product.
Going over is allowed rather than blocked. What refuses is going over with no balance to cover it: 402, code insufficient_balance. GET /v1/usage shows how much of the month is gone, so a job can check before it starts.
Nothing is charged inside the allowance, so if you never go over you never need a balance at all.
/v1/proxy/chat/completions
Redact, call your model with your key, restore the answer. One request.
You keep your vendor contract, your rates and your own logs. We never see the bill, because it is not ours — your provider key is forwarded to the one call that needs it and is never stored, logged or written down.
Request
POST /v1/proxy/chat/completions
Authorization: Bearer sai_... your Secure AI key
X-Provider-Key: sk-ant-... your model vendor's key
X-Provider: anthropic optional; only when we cannot tell
Content-Type: application/json
The body is an ordinary OpenAI chat completion request.
json
{
"model": "claude-sonnet-5",
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Draft a reply to Sara Whitfield on 07700 900123" }
],
"stream": false
}
model is required and must be one your own key can call — there is no automatic model choice here, because choosing a model is a decision about cost and the cost is yours. The vendor is worked out from the model name where we recognise it; send X-Provider when we do not.
Extra fields
Beyond the OpenAI shape, four fields are understood. All optional.
Field
What it does
map
A map from a previous call, so the same person keeps the same stand-in across turns
allow
Terms to send as written — a company mailbox, a product name a detector keeps mistaking for a person
profile
{ name, email, phone } belonging to the end user, so their own details are recognised even when written unusually
strict
true refuses the request rather than sending it when the name check cannot run. See Strict mode
Response
Ordinary OpenAI shape, with the real values already restored.
usage is your vendor's own count, passed through. We do not bill on it.
Streaming works the same way: set "stream": true and read server-sent events in OpenAI's format. The stand-ins are put back inside the stream, so no chunk you receive ever contains one.
With the OpenAI SDK
python
from openai import OpenAI
client = OpenAI(
api_key="sai_...",
base_url="https://secureai.one/v1/proxy",
default_headers={"X-Provider-Key": "sk-ant-..."},
)
reply = client.chat.completions.create(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Draft a reply to Sara on 07700 900123"}],
)
Nothing else in your code changes.
/v1/redact and /v1/restore
The same work, in two calls, with your model call in the middle. Use this when you want to see and control exactly what leaves your systems.
Redact
POST /v1/redact
Authorization: Bearer sai_...
Content-Type: application/json
json
{ "text": "Hi, I am Sara Whitfield. Ring me on 07700 900123 or email sara@example.com" }
Response:
json
{
"object": "redaction",
"text": "Hi, I am Sylvie Nakashima. Ring me on (415) 637-6526 or email ingrid.sinclair@outlook.com",
"map": {
"Sylvie Nakashima": "Sara Whitfield",
"(415) 637-6526": "07700 900123",
"ingrid.sinclair@outlook.com": "sara@example.com"
},
"names_decided": true,
"redacted": 6
}
The stand-ins are believable people rather than [REDACTED] markers, on purpose: a model given [NAME] writes about [NAME], and the answer comes back needing repair. A model given a name writes normally.
map is keyed by stand-in and valued by the real thing. redacted counts the entries; names_decided says whether the name detection actually ran.
Optional fields: map, allow, profile, strict — as above.
Restore
Send the model's answer back with the same map.
POST /v1/restore
json
{
"text": "I will ring Sylvie Nakashima on (415) 637-6526.",
"map": { "Sylvie Nakashima": "Sara Whitfield", "(415) 637-6526": "07700 900123" }
}
json
{
"object": "restoration",
"text": "I will ring Sara Whitfield on 07700 900123.",
"restored": 2
}
An empty map is not an error: text with nothing personal in it round-trips through both endpoints unchanged, and your code should not have to branch on that.
We do not keep the map
The map goes back with the response and is stored nowhere. That is a deliberate limit rather than an omission: keeping it would mean holding, for every customer, exactly the table that turns redacted text back into real people — which is the database this product exists in order not to be.
The consequence is yours to handle. If you lose a map, that answer cannot be restored. Hold it for the life of the conversation, in your own system, wherever you already keep the conversation itself.
This is also the argument for the proxy endpoint, where there is no map to lose.
/v1/chat/completions
Our models, our provider accounts, metered against a balance you buy in advance. Same OpenAI shape as the proxy, without the X-Provider-Key header.
model may be omitted, in which case a model is chosen by how hard the question is. Named routes are secureai-auto, secureai-plus, secureai-pro and secureai-ultra.
Spending is checked before the call and settled after: the most a request could cost is held, and the difference is released once the answer is in. A balance cannot go negative, so a runaway loop stops rather than arrives as an invoice.
Authenticated with the API key, so a long job can check its own balance before starting.
Strict mode
Identifying detail is found in two ways. Structured things — phone numbers, emails, addresses, card numbers — are found by rules that always work. Names belonging to other people are found by a model, and that check is best-effort: it can be slow, and it can return something unparseable.
By default, a failed name check does not stop the request. The structured stages have already run, and refusing to answer in order to protect a name that may not be there is its own kind of harm.
"strict": true asks for the other trade. When the name check cannot decide, the request is refused with 503 and nothing is sent to any model. It never fires when the check decided there was nothing to find.
If you are pushing customer records through this rather than one person's messages, you probably want strict mode on.
Errors
Errors come back in OpenAI's shape, so an SDK unwraps them without help.
json
{
"error": {
"message": "Not enough API balance. $0.00 left; top up to keep going.",
"type": "insufficient_quota",
"code": "insufficient_balance",
"param": null
}
}
Status
Means
400
The request was wrong — no model named, no provider key, a body that is not JSON
401
No API key, or one that is not recognised
402
Either no API access on this account, or out of balance. The code says which
403
Your plan does not include that model
413
Too much text in one request
429
A rate limit, or a spending cap reached
503
Redaction could not run, so nothing was sent
The status is left alone deliberately. Your SDK's retry policy is built on it, and softening a 429 into something friendlier turns a backoff into a hammering.
On the proxy endpoint, a refusal from your vendor is passed back with its own status and a message saying whose refusal it is — a bad key or an exhausted quota is an answer about your account, not about ours.
Limits
Text per /v1/redact call
200,000 characters
Input per chat request
16k–96k tokens, by plan
Requests
60 a minute, per key, per location
Included requests
50,000 a month, then $0.001 each
Keys per account
20
Use it from an assistant
There is an MCP server, so Claude, Cursor or an agent you wrote yourself can use the redaction as a tool without anybody writing the calls.
https://secureai.one/mcp
Add it as a remote MCP server with your key as a bearer token. In a client that takes JSON:
It offers two tools, redact and restore, which are the two endpoints above. The assistant decides when to reach for them; the instructions it gets tell it to redact before sending anything onward and to keep the map for the reply.
Tool calls count against the same monthly allowance as everything else.
language model reads, for anybody asking an assistant what this does.
Keys
Created and revoked in the app, under Settings → Developer.
A key may carry an optional monthly spending limit. It is not a second balance — the balance is the money — it is a wall for the key going into something still being written, so one bad loop cannot spend a month of it in an afternoon. Leave it empty for the key your own service runs on: the balance already bounds it, and a limit nobody chose refusing a legitimate job at three in the morning is the worse failure.
Revoking takes effect immediately and cannot be undone. Make another.