API documentation

Security models, one API call away.

unbreach speaks the OpenAI chat completions format. Keep your SDK, point it at unbreach, pick a model. Every request is metered per token and nothing you send is stored.

Base URL
https://api.unbreach.io/v1
Authentication
Authorization: Bearer ubk_live_…
Format
OpenAI-compatible JSON, streaming over SSE
Models
3 security models, from $0.15 per 1M tokens

The API is for companies. Request access for yours: we review each request within one business day. People who want to chat with unbreach use the app with a plan instead.

Quickstart

From approval to your first answer in about five minutes.

  1. 1

    Get access

    Request API access with your work email. Once we approve your organization, you get an email and the console opens Projects, API keys and Billing.

  2. 2

    Add credits and create a key

    In the console, add credits (card, bank, PayPal, crypto or wire), then API keys, Create API key. Copy the secret: it's shown once. Keep it in an environment variable, never in your code.

    Terminal
    export UNBREACH_API_KEY="ubk_live_..."
  3. 3

    Make a request

    With the OpenAI SDK, only the base URL and the key change.

# pip install openai
from openai import OpenAI
import os

client = OpenAI(
    base_url="https://api.unbreach.io/v1",
    api_key=os.environ["UNBREACH_API_KEY"],
)

resp = client.chat.completions.create(
    model="rampart-1",
    messages=[
        {"role": "system", "content": "You are a SOC analyst. Answer with a verdict and next steps."},
        {"role": "user", "content": "3 failed SSH logins for root from 185.220.101.4, then a success for svc-backup."},
    ],
)
print(resp.choices[0].message.content)
print(resp.usage)  # prompt_tokens, completion_tokens: what you're billed for
Response 200 OK
{
  "id": "chatcmpl-8f2c41",
  "object": "chat.completion",
  "created": 1793638800,
  "model": "rampart-1",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "Verdict: likely credential compromise. 185.220.101.4 is a Tor exit..."
    },
    "finish_reason": "stop"
  }],
  "usage": { "prompt_tokens": 41, "completion_tokens": 63, "total_tokens": 104 }
}

Authentication

Send your key in the Authorization header of every request. x-api-key: ubk_live_… works too, for tools that can't set a bearer token.

Header
Authorization: Bearer ubk_live_4cYq…40 characters

Keys start with ubk_live_ followed by 40 random characters. We store only a hash of each secret: if you lose it, roll the key or create a new one.

What a key can do

Each key belongs to a project of your organization. In the console you can narrow it down:

  • Models. All models, or only the ones you list. Projects can restrict models too.
  • Monthly spend limit. The key stops at this amount until the 1st of next month.
  • IP ranges. Requests from other addresses are refused, in addition to your organization's allowlist.
  • Expiry date. After it, the key answers expired_api_key.
  • Service accounts. Keys that don't belong to a person and survive team changes.

Rolling a key

Roll secret gives you a new secret right away. The previous one keeps working for 24 hours, so you can deploy without downtime. Revoking a key stops it at once.

Never ship a key in a browser, a mobile app or a public repository. Call unbreach from your server. If a key leaks, revoke it in API keys: the audit log shows who created and used it.

Chat completions

POST/v1/chat/completions

Sends a conversation to a model and returns its answer. Same request and response as OpenAI's chat completions, so existing code and SDKs work unchanged.

Request body

modelstringrequired

The model ID, like sentry-1, rampart-1 or bastion-1. See Models.

messagesarrayrequired

The conversation so far. Each message has a role (system, user or assistant) and a content string. Put logs, emails or code straight into the content.

streambooleandefault false

Send the answer as it's written, as server-sent events. See Streaming.

stream_options.include_usagebooleandefault false

With stream, adds a last chunk with the token counts. You're billed either way.

max_tokensintegeroptional

The most tokens the answer can use. Output tokens cost more than input tokens: cap them for high-volume triage.

temperature, top_p, stop, …

Other chat completion parameters go to the model as you send them.

Bodies can be up to 16 MB. The context window of each model limits how much of it the model reads.

Response

A chat.completion object. model is always the public ID you asked for, and usage holds the token counts you're billed for.

choices[0].message.contentstring

The answer.

choices[0].finish_reasonstring

stop when the model finished, length when it hit max_tokens or the context window.

usageobject

prompt_tokens, completion_tokens and total_tokens.

Response headers

HeaderWhenMeaning
x-request-idAlwaysOur ID for the request, like req_…. Include it when you write to us.
retry-after429, 503Seconds to wait before trying again.
deprecationDeprecated modeltrue: the model will be retired. See Deprecations.
sunsetDeprecated modelThe date it stops answering, as an HTTP date.

Streaming

With "stream": true, the answer arrives as server-sent events: one chat.completion.chunk per piece of text, then data: [DONE]. Show text to your analysts as it's written, or stop reading early: you pay for what was generated.

stream = client.chat.completions.create(
    model="bastion-1",
    messages=[{"role": "user", "content": "Reconstruct the attack chain from these logs: ..."}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices:
        print(chunk.choices[0].delta.content or "", end="", flush=True)
    if chunk.usage:
        print("\n", chunk.usage)
Events
data: {"object":"chat.completion.chunk","model":"bastion-1","choices":[{"index":0,"delta":{"content":"The attack "}}]}

data: {"object":"chat.completion.chunk","model":"bastion-1","choices":[{"index":0,"delta":{"content":"chain is: "}}]}

data: {"object":"chat.completion.chunk","model":"bastion-1","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"object":"chat.completion.chunk","model":"bastion-1","choices":[],"usage":{"prompt_tokens":812,"completion_tokens":356,"total_tokens":1168}}

data: [DONE]

The usage chunk comes only when you ask for it with include_usage. If the connection drops, we bill the tokens generated until then.

List models

GET/v1/models

The models your key can use, with their context window. Models in preview or being retired say so.

Request
curl https://api.unbreach.io/v1/models -H "Authorization: Bearer $UNBREACH_API_KEY"
Response 200 OK
{
  "object": "list",
  "data": [
    { "id": "sentry-1", "object": "model", "owned_by": "unbreach", "context_window": 32000 },
    { "id": "rampart-1", "object": "model", "owned_by": "unbreach", "context_window": 128000,
      "status": "deprecated", "retires_at": "2027-03-01T00:00:00.000Z", "replacement": "rampart-2" },
    { "id": "rampart-2", "object": "model", "owned_by": "unbreach", "context_window": 256000, "status": "preview" }
  ]
}

An example: status, retires_at and replacement appear only when they apply.

Errors

Errors use the OpenAI format, with an HTTP status and a stable code you can branch on. The message is written for people and can change.

Response 429 Too Many Requests
{
  "error": {
    "message": "Rate limit reached. Retry in 12 seconds.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "param": null
  }
}
StatusCodeWhat happenedWhat to do
400invalid_requestThe body isn't JSON with model and messages.Fix the request. Don't retry.
401invalid_api_keyThe key is missing, wrong or revoked.Check the header and the key in the console.
401expired_api_keyThe key passed its expiry date.Create a new key.
403organization_not_approvedYour organization's API access is still in review, or was declined.Wait for our email, or update the request in the console.
403organization_suspendedWe suspended your organization's API access.Write to [email protected].
403ip_not_allowedThe request came from an address outside the key's or the organization's allowlist.Add the address in the console, or call from an allowed one.
403model_not_allowedThe key or its project can't use this model.Use an allowed model, or change the key.
404model_not_foundNo model has this ID.Check the spelling against /v1/models.
410model_retiredThe model was retired. The message names its replacement.Switch to the replacement.
429rate_limit_exceededToo many requests per minute for this model or project.Wait retry-after seconds, then retry.
429insufficient_quotaYour credit balance is empty.Add credits, or turn on auto-recharge.
429key_limit_reachedThe key reached its monthly spend limit.Raise the limit, or wait for the 1st.
429project_budget_reachedThe project reached its monthly budget.Raise the project budget.
429budget_exceededYour organization reached its monthly budget, set to stop requests.Raise the budget in Limits.
502upstream_error
upstream_unavailable
The model failed or couldn't be reached. Not billed.Retry with backoff.
503model_unavailable
endpoint_unavailable
The model or the endpoint is paused, for maintenance for example. The message says why.Wait retry-after seconds.
504upstream_unavailableThe model took too long to answer. Not billed.Retry, or stream long answers.

When the model itself refuses a request (a prompt longer than its context window, for example), you get its status and message as they are.

Models

Three sizes of the same security training. Start with rampart-1; move high-volume filtering to sentry-1 and hard investigations to bastion-1.

ModelBest forContextInputOutput
sentry-1Fast triage32K$0.15$0.60
rampart-1Everyday analysis128K$1.20$4.80
bastion-1Deep investigations200K$5.00$20.00

Prices in USD per million tokens, before tax. This table shows today's prices.

Deprecations

Models move through four stages. We email the owners and admins of organizations that used a model in the last 30 days before it's retired.

Preview

Open to every organization. Behavior and price can still change: test it before you move traffic.

Active

Generally available and stable.

Deprecated

Still answers, with deprecation: true and a sunset date in the headers, and a replacement in /v1/models.

Retired

Answers 410 model_retired with the name of the replacement. Gone from /v1/models.

To catch deprecations early, log a warning when a response carries the deprecation header.

Rate limits

Limits count requests per minute, per model, for your whole organization. Your usage tier sets them; the console shows yours in Limits. A project can also have its own limit, lower than the organization's.

ModelTier 1Tier 2Tier 3Tier 4
sentry-15003,00010,00030,000
rampart-12001,0005,00015,000
bastion-1503001,0003,000

Requests per minute. New organizations start at tier 1. Need more? Use Request higher limits in the console.

Over the limit, you get 429 rate_limit_exceeded with a retry-after header. See Retries.

Pricing and usage

You pay per token, input and output, at the price of the model. No subscription and no minimum.

  • Counted by the model. We bill the usage the model reports in each response. In the rare case it reports none, we estimate about 4 characters per token.
  • Failed requests are free. Errors before or from the model aren't billed.
  • Volume discount. Monthly spend above $1,000 costs 20% less, automatically.
  • Two ways to pay. Prepaid credits by default: requests stop when the balance is empty, and auto-recharge tops it up. Established companies can switch to a monthly invoice with net terms.

Example

A triage pipeline sends 2,000 alerts a day to sentry-1, about 600 input and 150 output tokens each: about $11 a month.

Usage shows in the console within seconds: per day, model, key, project and member, with a CSV export.

Spend controls

Set limits at every level, so one runaway script can't spend your month.

ControlWhereWhen it's reached
Key spend limitAPI keys, edit a keyThat key gets key_limit_reached. Other keys keep working.
Project budgetProjectsKeys of that project get project_budget_reached.
Organization budgetLimitsEmail alerts at the thresholds you pick (50%, 80%, 100%). Set to stop, every key gets budget_exceeded.
Low balance alertBillingOwners and billing members get an email.

Every limit resets on the 1st of the month, in UTC.

Data and security

Zero data retention

Request and response bodies are never logged or stored, and never used for training. We keep only token counts for billing.

Hashed keys

We store a SHA-256 hash of each secret, never the secret. Rolling and revoking take effect at once.

IP allowlists

Per key and per organization, IPv4 and IPv6, with CIDR ranges.

Team controls

Roles, SSO with SAML or OIDC, SCIM provisioning, required two-factor authentication and an audit log.

Read the privacy policy for the details, or ask for our security documentation.

Retries

Retry 429 and 5xx answers, waiting longer each time and honoring retry-after. Never retry other 4xx errors: the request itself needs fixing. The OpenAI SDKs already retry twice; raise it for batch jobs.

client = OpenAI(
    base_url="https://api.unbreach.io/v1",
    api_key=os.environ["UNBREACH_API_KEY"],
    max_retries=5,   # 429 and 5xx, with backoff and retry-after
    timeout=120,
)

Going to production

  • One key per service and environment, in its own project, so you can see and cap each one.
  • Keys in a secret manager, never in code or front ends. Restrict them by IP when your servers have fixed addresses.
  • Budgets and alerts on the organization and on each project.
  • Auto-recharge, or monthly invoicing, so production never stops on an empty balance.
  • Retries on 429 and 5xx, and a timeout that fits your longest answers.
  • Log x-request-id with your own request IDs, and watch for the deprecation header.

Ready to build?

Request access for your company. We answer within one business day.