Quickstart
Two lines, and your requests have a policy.
Alectura speaks each provider’s own API. You change the base URL and the API key, add headers that say who each request is for, and everything else is the provider’s API, passed through unchanged.
Set up an environment
In the dashboard, create a project and an environment in the US, the EU or Australia. The region is fixed when the environment is created, and its requests are processed there.
Add a connection: a provider account of yours, on OpenAI, Anthropic, Amazon Bedrock or Google Gemini, with its data terms. To try Alectura first, choose the simulator instead: it needs no provider key, answers in every provider’s format with recorded responses, reaches no provider and costs nothing, and an environment can send it 60 requests a minute. Then create a sending key. It names its region and mode:
sk-alectura-{region}-{mode}-{secret}{mode} is live for an environment marked as production and test for any other. A key sent to another region’s host is refused with wrong_region, and the message names the right host.
Point your SDK at Alectura
Keep the SDK you use. The key goes in whichever header it already sends, and the base URL is your region’s:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://eu.gateway.alecturalabs.com/openai/v1",
api_key=os.environ["ALECTURA_API_KEY"],
)
response = client.responses.create(
model="gpt-5-mini",
input="Say hello in French.",
extra_headers={
"Alectura-Organization": "globex",
"Alectura-User": "user_42",
"Alectura-Plan": "enterprise",
},
)
print(response.output_text)
import os
from anthropic import Anthropic
client = Anthropic(
base_url="https://eu.gateway.alecturalabs.com/anthropic",
api_key=os.environ["ALECTURA_API_KEY"],
)
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Say hello in French."}],
extra_headers={"Alectura-Organization": "globex", "Alectura-User": "user_42"},
)
print(message.content[0].text)
import os
from google import genai
client = genai.Client(
api_key=os.environ["ALECTURA_API_KEY"],
http_options={
"base_url": "https://eu.gateway.alecturalabs.com/gemini",
"headers": {"Alectura-Organization": "globex", "Alectura-User": "user_42"},
},
)
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Say hello in French.",
)
print(response.text)
- The two lines that change
- Who the request is for
| OpenAI SDKs | https://us.gateway.alecturalabs.com/openai/v1 |
|---|---|
| Anthropic SDKs | https://us.gateway.alecturalabs.com/anthropic |
| Gemini SDKs | https://us.gateway.alecturalabs.com/gemini |
| OpenAI SDKs | https://eu.gateway.alecturalabs.com/openai/v1 |
|---|---|
| Anthropic SDKs | https://eu.gateway.alecturalabs.com/anthropic |
| Gemini SDKs | https://eu.gateway.alecturalabs.com/gemini |
| OpenAI SDKs | https://au.gateway.alecturalabs.com/openai/v1 |
|---|---|
| Anthropic SDKs | https://au.gateway.alecturalabs.com/anthropic |
| Gemini SDKs | https://au.gateway.alecturalabs.com/gemini |
Claude on Amazon Bedrock is reached through the Anthropic SDK: the environment’s policy decides whether a request goes to Anthropic or to Bedrock.
Say who each request is for
Policies match on these headers, and limits, signals and suspensions count by them. The values are your own identifiers.
| Header | Carries |
|---|---|
Alectura-Organization | The team's own identifier for the organization the request is for |
Alectura-User | The team's own identifier for the user |
Alectura-Plan | The plan the organization is on |
Alectura-Conversation | Optional. Requests that share it form a conversation; without it, conversations are linked by their blocks. |
- Each value is 1 to 256 characters of printable ASCII. A missing header is null, and policies can match null.
- Any other
Alectura-*header is refused, so a typo can’t quietly make a request anonymous. - OpenAI, Anthropic and Bedrock get a keyed hash of the user as its safety identifier, never your identifier itself. Gemini’s API has no such field.
Read what comes back
A response is the provider’s own, with a few headers added:
Alectura-Request-Id | On every response, refusals included. Every error body carries it too. |
Alectura-Connection | The connection that served the request, when a provider was reached. |
Alectura-Region | The region that served it. |
Retry-After | On a per-minute limit, in seconds. |
A refusal comes back in the provider’s own error shape, so your error handling already works, and the provider never sees the request. On the OpenAI formats:
{
"error": {
"message": "Requests for this user are suspended.",
"type": "alectura_policy",
"param": null,
"code": "user_suspended",
"request_id": "req_01M3PQJ64T…"
}
}
Each code has one status. Only the codes that waiting can fix get a status SDKs retry on their own: 429 and the 5xx.
| Status | Codes |
|---|---|
400 | invalid_requestconflicting_api_keys |
401 | invalid_api_key |
402 | budget_exhausted |
403 | permission_deniedwrong_regionuser_suspendedorganization_suspendedmodel_not_allowedregion_not_allowedno_allowed_connectionblocked_by_guardrailattachment_uninspectablecut_by_guardrail |
404 | unsupported_endpoint |
413 | request_too_large |
429 | rate_limited |
502 | provider_unreachable |
503 | limiter_unavailable |
504 | provider_timeout |
Send your first request.
Then turn on a guardrail in monitor mode, and watch what it finds.