---
title: jusInfer API reference
description: Complete reference for the jusInfer gateway — Chat Completions, Responses, auth, errors, rate limits.
tldr: Base URL is https://api.jusinfer.com. Auth is Bearer token — either a long-lived jinf_ API key or a short-lived Firebase JWT. Inference endpoints are /v1/chat/completions and /v1/responses, both OpenAI-compatible. Rate limits are 60 RPM per key, 600 RPM per tenant.
slug: api-reference
order: 4
updated: 2026-05-25
---

# jusInfer API reference

The jusInfer gateway is a thin OpenAI-compatible proxy in front of a model selector. Everything except `/admin/*` is open via CORS to browser callers and accepts CORS preflights for `https://jusinfer.com`, `https://*.jusinfer.com`, and `http://localhost:*`.

**Base URL:** `https://api.jusinfer.com`

## Authentication

Two surfaces:

- **`jinf_`** keys — long-lived bearer tokens for code agents / CLIs. Mint at [/developer](https://jusinfer.com/developer).
- **Firebase JWT** — short-lived ID token for first-party browser sessions on `jusinfer.com`. Used by the dashboard.

Both go in the `Authorization` header:

```
Authorization: Bearer <jinf_… or JWT>
```

## Inference endpoints

### `POST /v1/chat/completions`

OpenAI Chat Completions. Schema is the standard one — `messages`, `tools`, `stream`, etc. Add anything OpenAI accepts and it'll be forwarded if the underlying model supports it.

**Request:**
```json
{
  "model": "jusInfer-auto",
  "messages": [
    {"role": "system", "content": "You are concise."},
    {"role": "user", "content": "Write a haiku about gradients."}
  ],
  "stream": false,
  "temperature": 0.7
}
```

**Response (non-stream):**
```json
{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 1779000000,
  "model": "jusInfer-auto",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "..."},
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 23,
    "completion_tokens": 17,
    "total_tokens": 40,
    "cost": 0.000086
  }
}
```

`usage.cost` is in USD and is **always** present on jusInfer responses, even when the upstream model doesn't report cost — we compute it from token counts × per-model rate.

### `POST /v1/responses`

OpenAI Responses API. Same auth, similar shape. Use this if your harness prefers the newer surface.

**Request:**
```json
{
  "model": "jusInfer-auto",
  "input": "Summarize the Goldilocks zone."
}
```

The gateway translates `input` into chat-style messages internally; the underlying selection logic is identical.

## Identity / onboarding

### `POST /v1/onboard` (JWT only)

Idempotent first-time setup. Creates a tenant for a Firebase user if they don't have one. The dashboard calls this on every sign-in.

```json
// → 200
{
  "tenant_id": "ten_...",
  "user_id": "usr_...",
  "is_new": false,
  "balance": 0
}
```

### `GET /v1/keys/self`

Returns the calling identity — works with both auth surfaces. Useful for client introspection.

```json
{
  "auth_kind": "jcg",
  "user": {"id": "usr_...", "email": "..."},
  "tenant": {"id": "ten_...", "name": "...", "balance": 1000000},
  "key": {"token_hash": "...", "name": "VS Code on macbook", "created_at": 1779000000}
}
```

## Key management (JWT only)

| Method | Path | What |
|---|---|---|
| `POST` | `/v1/keys` | Mint a new jinf_ key. Body: `{ name?, client_label?, expires_in_days? }`. Returns plaintext **once**. |
| `GET`  | `/v1/keys` | List your keys (without plaintext). |
| `DELETE` | `/v1/keys/:hash` | Revoke. |
| `POST` | `/v1/keys/:hash/rotate` | Revoke + mint replacement preserving name/label/scope. |

## Usage

| Method | Path | What |
|---|---|---|
| `GET` | `/v1/usage/me?days=30` | Per-day usage for the calling user. |
| `GET` | `/v1/usage/tenant?days=30&group_by=day|user|none` | Tenant-wide (owner only). |

## Tenant & invites (JWT only)

| Method | Path | What |
|---|---|---|
| `GET`  | `/v1/tenant` | Tenant info + balance. |
| `POST` | `/v1/tenant/rename` | Rename. |
| `GET`  | `/v1/tenant/members` | List members + caps. |
| `POST` | `/v1/tenant/members/:id/cap` | Set per-user soft monthly cap. |
| `POST` | `/v1/tenant/members/:id/remove` | Remove member (owner only). |
| `POST` | `/v1/tenant/transfer-ownership` | Transfer to another member. |
| `POST` | `/v1/tenant/invites` | Send Resend invite email. |
| `GET`  | `/v1/tenant/invites` | List pending invites. |
| `DELETE` | `/v1/tenant/invites/:id` | Cancel pending invite. |
| `POST` | `/v1/tenant/invites/:id/resend` | Resend. |
| `POST` | `/v1/invites/accept` | Accept invite with token (email-match guard). |

## Billing

| Method | Path | What |
|---|---|---|
| `GET`  | `/v1/billing/packs` | List credit packs ($5/$10/$25/$50). |
| `GET`  | `/v1/billing/purchases` | History — includes presentment currency when not USD. |
| `POST` | `/v1/billing/checkout` | Create Stripe Checkout for a pack. |
| `GET`  | `/v1/billing/seats` | Seat-sub status + entitlement check (`{ok:true}` or `{ok:false, code}`). |
| `POST` | `/v1/billing/seats/checkout` | Create seat subscription. |
| `POST` | `/v1/billing/seats/update` | Change seat count. |
| `POST` | `/v1/billing/seats/cancel` | Cancel at period end. |

## Store

| Method | Path | What |
|---|---|---|
| `POST` | `/v1/store/preorder` | Reserve a pre-order. No auth required; if JWT supplied, links to user. Body: `{product_id, product_name, email, size, qty}`. |

## Errors

All errors are JSON with `code` (machine-readable) and `error` (human):

```json
{ "code": "INSUFFICIENT_CREDITS", "error": "Wallet has $0.01; this call needs $0.02." }
```

Common codes:

| Code | Status | Meaning |
|---|---|---|
| `MISSING_TOKEN` | 401 | No `Authorization` header |
| `INVALID_KEY` | 401 | jinf_ key unknown / revoked / expired |
| `INVALID_TOKEN` | 401 | JWT verification failed |
| `NEEDS_ONBOARD` | 401 | Valid JWT but no tenant — call `POST /v1/onboard` |
| `JWT_REQUIRED` | 403 | Endpoint requires JWT, not jinf_ |
| `OWNER_REQUIRED` | 403 | Endpoint requires tenant owner |
| `SEAT_REQUIRED` | 403 | Multi-member tenant needs seat subscription |
| `INSUFFICIENT_CREDITS` | 402 | Wallet empty; top up |
| `USER_CAP_EXCEEDED` | 402 | Per-user soft cap reached this month |
| `RATE_LIMIT` | 429 | 60/min per key, 600/min per tenant |
| `INVALID_EMAIL` / `INVALID_SIZE` / `ALREADY_RESERVED` | 400/409 | Store endpoint validation |
| `UPSTREAM_ERROR` | 502 | Upstream model temporarily unavailable |

## Rate limits

- **Per key:** 60 requests per minute
- **Per tenant:** 600 requests per minute

Raisable per-tenant by request — DM us once you're consistently bumping the cap.

## CORS

Allowed origins:

- `https://jusinfer.com`
- `https://www.jusinfer.com`
- `https://jusinfer-bd8e5.web.app`
- `https://jusinfer-bd8e5.firebaseapp.com`
- `http://localhost:3000`
- `http://localhost:5173`

`/admin/*` is **not** CORS-enabled — server-to-server only.

## Versioning

The gateway uses path-level versioning (`/v1/...`). Breaking changes get `/v2/...`. `usage.cost` and other extensions stay backwards-compatible.

## Related

- [Use jusInfer with Claude Code](/docs/claude-code/)
- [Use jusInfer with OpenCode](/docs/opencode/)
- [OpenAI-compatible drop-in](/docs/openai-drop-in/)

---

*Agent-readable raw markdown:* [/docs/api-reference.md](/docs/api-reference.md)
