Authentication

Authentication

Every endpoint except GET /models and GET / needs a key. There are two accepted
header styles, and they are equivalent: pick whichever your client already emits.

The two styles

Bearer
x-api-key
Authorization: Bearer sk-your-key-here

The OpenAI style. Any OpenAI SDK sends this on its own once api_key is set.

x-api-key: sk-your-key-here

The Anthropic style. Claude Code, Cursor and the Anthropic SDKs send this. api-key and
x-goog-api-key are accepted as well, for clients with their own spelling.

A key that works for a completion also works for your bill, so the wallet endpoints use
the same lookup rather than a second credential system.

Padding is tolerated

RFC 7230 allows spaces around a header value and some SDKs send them. The key is trimmed
before it is resolved, so a trailing newline pasted from a dotfile does not produce a
401.

What a bad key looks like

{
  "error": {
    "message": "Invalid API key (invalid_api_key)",
    "type": "authentication_error",
    "param": null,
    "code": "invalid_api_key",
    "support": "[email protected]",
    "docs": "https://docs.hisoka.ai/errors/",
    "request_id": "01M73N2HP4HDDZH803J1C120HT"
  }
}

That is one code covering four situations: missing, malformed, revoked, and expired. All
four are fixed the same way, by issuing a new key, so the distinction is not worth a
separate code.

IP allowlists

A key may carry an allowlist. A caller outside it gets 403 ip_not_allowed, and the
allowlist is checked per request rather than only at creation, so narrowing it takes
effect immediately for keys already in use.

Limits worth knowing

  • Keys are shown once. The plaintext exists at creation and never again; the server
    keeps only a hash. If you lose it, revoke and create another.
  • A revoked key is dead immediately. There is no grace period, so rotate by creating
    the replacement first.
  • There is an active-key ceiling per account. Revoking a key frees a slot; expired and
    revoked keys do not count against you, so rotating does not lock you out.
Never ship a key in a client application

A key in a browser bundle or a mobile binary is a key you have published. Proxy through
your own backend and keep the key server-side. Every call is billed to the key’s owner,
so a leaked key is your balance, not someone else’s problem.

Using it from an SDK

from openai import OpenAI

client = OpenAI(
    api_key="sk-your-key-here",
    base_url="https://hisoka.ai/api/v1",
)
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk-your-key-here",
  baseURL: "https://hisoka.ai/api/v1",
});
Why the base URL has no /v1 twice

The SDKs append their own /v1 to the paths they call, so base_url ends at /api/v1
and the SDK builds /api/v1/chat/completions. Set it one level deeper and every request
404s. The gateway collapses a repeated version segment, but it will never strip a single
one, because that would break clients that send it exactly once.