Hailey · API Reference

One endpoint.
Every answer. ask.hailey.io

An OpenAI-compatible AI gateway. Point any OpenAI-style client at Hailey, authenticate with your service key, and get streaming or complete responses — with durable per-project context no vanilla endpoint gives you.

hailey — connection
Base URL https://ask.hailey.io/v1
Auth Authorization: Bearer hly_… get a key
drop-in OpenAI SDK compatible 5 models · one model id picks the engine SSE streaming projects durable context parallel by default
01

Quickstart

Three lines of setup in any OpenAI client — only the base URL changes.

shell
curl https://ask.hailey.io/v1/chat/completions \
  -H "Authorization: Bearer $HAILEY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4-8",
    "messages": [{"role": "user", "content": "Hello, Hailey."}]
  }'
python
from openai import OpenAI

client = OpenAI(
    base_url="https://ask.hailey.io/v1",
    api_key="hly_…",
)

resp = client.chat.completions.create(
    model="claude-opus-4-8",
    messages=[{"role": "user", "content": "Hello, Hailey."}],
)
print(resp.choices[0].message.content)
typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://ask.hailey.io/v1",
  apiKey: process.env.HAILEY_API_KEY,
});

const resp = await client.chat.completions.create({
  model: "claude-opus-4-8",
  messages: [{ role: "user", content: "Hello, Hailey." }],
});
console.log(resp.choices[0].message.content);
02

Authentication

Every request needs a service key in the Authorization header: Authorization: Bearer hly_…. Keys are created per integration on the API keys page, are shown once at creation, and can be revoked at any time.

Each key can carry a default project — the context it runs in when a request doesn't name one (see Projects).

03

Models & engines

GET /v1/models lists the models your key can use. A model id pins the engine (the AI backend adapter), the model family, and the version in one string — pick the id, and the gateway routes to the right backend.

Model idEngineContext windowMax output
claude-opus-4-8 anthropic 200,000
32,000
claude-opus-4-7 anthropic 200,000
32,000
claude-sonnet-4-6 anthropic 200,000
64,000
claude-haiku-4-5 anthropic 200,000
8,192
claude-fable-5 anthropic 200,000
64,000

Additional engines (e.g. OpenAI-family models) appear in this list as adapters are enabled — your integration code doesn't change, you just select a different model id. Calling a registered model whose engine isn't enabled yet returns 501 engine_not_available. If a request omits model, the project's default model is used.

04

Chat completions

POST /v1/chat/completions — the OpenAI chat-completions shape. Minimal request:

request
{
  "model": "claude-opus-4-8",
  "messages": [
    {"role": "system", "content": "Answer in one sentence."},
    {"role": "user",   "content": "What is Hailey?"}
  ]
}
response
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "model": "claude-opus-4-8",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "…" },
    "finish_reason": "stop"
  }],
  "usage": { "prompt_tokens": 123, "completion_tokens": 45, "total_tokens": 168 }
}

Send the full messages history each call, exactly as with OpenAI — prior user/assistant turns are treated as conversation context. system messages are appended to the project's own system prompt (project instructions always apply first).

Image input

A user message may carry images using the OpenAI content-array form. Send each image as a base64 data: URI in an image_url part:

request
{
  "model": "claude-opus-4-8",
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "What does the label on this jar say?"},
      {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ…"}}
    ]
  }]
}

JPEG, PNG, GIF and WebP; up to 5 MB per image, 8 images and 20 MB per request, 8000 px per side. The type is read from the image itself, not from the URI. Remote http(s) image URLs are not fetched — encode the image and send it. Images work with streaming and with response_format, and may sit in any user turn of the history. An image that cannot be accepted is rejected with a 400; it is never silently dropped.

05

Streaming

Set "stream": true to receive server-sent events of chat.completion.chunk objects, terminated by data: [DONE] — the standard OpenAI streaming contract, so SDK streaming helpers work unchanged. The final chunk carries usage.

python
stream = client.chat.completions.create(model="…", messages=[…], stream=True)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")
06

Projects — durable context

A plain gateway call is stateless: it runs in a fresh, empty sandbox. A project gives your calls durable context — the gateway equivalent of running inside your project directory.

A project is a Hailey entity. It exists only in your Hailey account — created at /settings/projects or provisioned for you — and is unrelated to any similarly-named concept elsewhere: it is not a claude.ai Project, not an OpenAI platform project, and not a folder on your machine. If a request names a project that doesn't exist in the calling key's account, the gateway returns 404 project_not_found.
What a project carriesEffect on every call
System promptAlways-on instructions, applied before any request system message.
CLAUDE.mdA project brief placed at the workspace root and auto-loaded by the engine.
Context filesSeeded into the call's workspace — the model can read them while answering.
DefaultsModel (engine + version), parameters, tool allowlist, workspace mode.

Create and manage projects at /settings/projects. Bind one to a key as its default, or select per request with hailey.project. Keys without a project use an auto-provisioned personal default project. A key can only reach projects owned by the same account.

Workspace modes

Calls run in parallel by default — including calls on the same key, project, and account. Fire as many concurrent requests as you need; nothing on the server queues them. Sequential execution is opt-in: pass "hailey": { "params": { "serial": true } } and calls to that project queue and wait their turn — each request blocks until the one ahead of it finishes, then runs. You only see 409 project_busy if a call waits longer than the server's queue cap (minutes) — an exceptional case, not the norm.

conversation default · parallel

Each call gets a fresh workspace seeded from the project's context files. File mutations are discarded after the call. Fully parallel — serialize with params.serial when order matters.

shared always serial

Calls run inside the project's persistent workspace — files written by one call are visible to the next. Two engines can't safely share one live workspace, so calls queue per project.

Conversations (shared mode)

In shared mode you may pass a conversation id of your choosing. The gateway maps it to a persistent engine session and resumes it on every call with the same id — server-side memory without re-sending history:

json
"hailey": { "workspace": "shared", "conversation": "onboarding-7c2" }
07

The hailey extension & headers

Hailey-specific options ride in an optional hailey object in the request body (use extra_body in the OpenAI SDKs). Clients that can't add body fields can send the equivalent headers.

request body
{
  "model": "claude-opus-4-8",
  "messages": [ … ],
  "hailey": {
    "project":      "birthday-gold",   // project slug or id (default: the key's project)
    "workspace":    "shared",          // 'conversation' (default) | 'shared'
    "conversation": "onboard-7c2",     // shared mode only: resume this session
    "params":       { "max_turns": 8 },// per-call engine parameter overrides
    "retain_days":  -1                 // -1 = keep no transcript; 1–365 = delete after N days
  }
}
header equivalents
X-Hailey-Project: birthday-gold
X-Hailey-Workspace: shared
X-Hailey-Conversation: onboard-7c2
X-Hailey-Retain-Days: -1
python sdk
client.chat.completions.create(
    model="claude-opus-4-8",
    messages=[{"role": "user", "content": "Summarize the seed data."}],
    extra_body={"hailey": {"project": "birthday-gold"}},
)
08

Actions — named tasks

An action is a task Hailey defines once and every site calls by name: the prompt, the output schema, the model and any scoring live in Hailey, not in your code. Fix or improve it once and every caller gets the change. An action must be granted to your account; one you can't use answers 404 action_not_found, the same as one that doesn't exist.

bash
curl https://ask.hailey.io/v1/actions -H "Authorization: Bearer $HAILEY_API_KEY"            # what you may call
curl https://ask.hailey.io/v1/actions/kyc-verify -H "Authorization: Bearer $HAILEY_API_KEY" # its inputs + images
curl https://ask.hailey.io/v1/actions/kyc-verify -H "Authorization: Bearer $HAILEY_API_KEY" \
  -H "Content-Type: application/json" -d '{
    "inputs": { "name_on_account": "Jane Doe", "postal_code": "80202", "challenge": "Give a thumbs-up next to your face" },
    "images": { "id_front": "data:image/jpeg;base64,…", "id_back": "data:image/jpeg;base64,…", "selfie": "data:image/jpeg;base64,…" }
  }'
200 response (kyc-verify)
{
  "object": "action.result", "id": "act_…", "action": "kyc-verify", "version": 1, "model": "claude-opus-4-8",
  "verdict": "review",                        // pass | review | fail — act on this
  "score": 76,                                // 0–100, in the verdict's band: pass 90–100, review 31–89, fail 0–30
  "components": { "document": 100, "name": 100, "face": 30, "liveness": 100 },
  "reasons": [ { "code": "face_not_assessed", "severity": "review", "message": "The automatic face comparison was not made." } ],
  "address_match": "match",                   // match | different | unread | not_checked — never decisive
  "extracted": { "name_on_id": "…", "expires_on": "2030-01-01", "document_number_last4": "1234", … },
  "checks": { … the model's raw answer … },
  "retention": "none",                       // standard | none | Nd — see retain_days
  "usage": { "prompt_tokens": 15469, "completion_tokens": 844, "total_tokens": 16313 }
}
ActionWhat it does
kyc-verifyReads a government photo ID, compares its name with the account name and its portrait with a live selfie, checks liveness and the requested gesture. fail: not an ID, expired, a different name, tampering or a sample card, or a high-confidence different face. pass: every check clear-cut. Anything else is review — have a person look. Never retained: no engine transcript, whatever retain_days says, and Hailey logs only verdict, score and timing.
Images are data URIs only, with the same limits as chat completions. A 502 means the action couldn't finish (engine error or no structured answer): retry later, or send the case to a person.
09

Parameters

Parameters resolve in three layers: project defaults → per-request hailey.params → standard OpenAI top-level fields. Support depends on the engine; unsupported parameters are accepted and ignored, never fatal.

ParameterWhereSupportBehavior (anthropic engine)
modeltop levelhonoredSelects engine, family, and version.
streamtop levelhonoredSSE streaming on/off.
messages (system)top levelhonoredAppended to the project system prompt.
max_turnshailey.params / projecthonoredCaps agentic tool-use turns (1–50, default 20).
max_thinking_tokenshailey.params / projecthonoredExtended-thinking budget (0–32000). Default 0: thinking off for the fastest answers; raise it for harder reasoning.
retain_dayshailey.retain_days / top level / X-Hailey-Retain-Days; actions: top levelhonoredTranscript retention for this call. Omitted: standard retention (a full transcript is kept; see Privacy). -1: no transcript is written. 1–365: the transcript is deleted after that many days. The response echoes the setting in the X-Hailey-Retention header (standard | none | Nd), or as retention on action results. You can't combine it with hailey.conversation. Actions marked never-retained (kyc-verify) answer none whatever you send.
serialhailey.params / projecthonoredDefault false: calls run fully in parallel, even within one account. Set true and calls queue per project, each waiting for the previous one to finish.
tool allowlistproject settinghonoredRestricts which tools the engine may use. Code-execution and file-mutation tools are always disabled on gateway calls.
temperature, top_p, max_tokenstop levelignoredAccepted for compatibility; the engine manages sampling and output budget.
10

Errors

Errors use the OpenAI error envelope:

json
{ "error": { "type": "authentication_error", "message": "Invalid API key.", "code": null } }
StatusCodeMeaning
401—Missing or invalid Authorization: Bearer hly_….
400invalid_workspace_mode, conversation_requires_shared, …Malformed request.
400image_url_not_supported, invalid_image, unsupported_image_type, image_too_large, too_many_imagesAn image part was refused: a remote URL instead of a data URI, bad base64, not a readable JPEG/PNG/GIF/WebP, over a size limit, or more than 8 images.
400invalid_retain_days, retention_conflictretain_days isn't -1 or 1–365, or it was sent with a conversation, which needs its transcript to resume.
404project_not_foundProject unknown to this account (or archived).
400invalid_action_inputAn action input or image is missing, unknown, the wrong type or too long.
404action_not_foundNo such action, or it isn't granted to your account.
404model_not_foundUnknown or disabled model id.
409project_busyA serialized call (shared mode or params.serial) waited longer than the queue cap. Exceptional — serialized calls normally queue and complete. Retry with backoff.
501engine_not_availableThe model's engine has no adapter enabled yet.
502upstream_errorThe engine failed mid-call. Safe to retry.
503engine_unavailableThe engine is temporarily unavailable (e.g. mid-update). Retry shortly.

Ready to build?

Request access, create a key, give it a project — and every call arrives briefed.

Request access Create a key See the live demo