API reference
Acest conținut nu este încă disponibil în limba selectată.
The razorBridge REST API is what the CLI uses, and it is
public: anything rb does, your own code can do.
Base URL
Section titled “Base URL”https://api.razorbridge.eu/v1A request to the bare root of that host returns a small JSON index of the main
endpoint groups. There is a staging deployment at
https://api.staging.razorbridge.eu/v1 with separate data.
Two conventions to note before you start:
- Paths under
/events/,/blades/,/credits/,/gpu-types/and/auth/end in a slash. The/gate/…paths do not. Getting this wrong produces a redirect or a 404. - Every response is JSON. Errors are always
{"error": "<message>"}, except for inference calls, which pass the upstream provider’s error body through unchanged.
Authentication
Section titled “Authentication”There are two kinds of credential, both presented as Authorization: Bearer <…>.
| Credential | Obtained from | Valid for |
|---|---|---|
| Session token | The device flow below, or rb auth login |
Every endpoint except the two marked gate key |
| Gate key | POST /gate/keys or the web app |
POST /gate/chat/completions and GET /gate/models only |
Presenting the wrong kind is a 401, which is the usual cause of an unexpected
Invalid API key.
Session access tokens expire after 1 hour; refresh tokens after 30 days.
The device flow
Section titled “The device flow”Made for command-line and headless clients: the client never sees a password.
POST /auth/device/ (no authentication)Response:
| Field | Notes |
|---|---|
device_code |
Secret. Poll with this |
user_code |
8 characters. Show it to the user to type in the browser |
verification_uri |
Where the user goes to approve |
expires_in |
900 — the code is good for 15 minutes |
interval |
5 — suggested seconds between polls |
curl -X POST https://api.razorbridge.eu/v1/auth/device/Then poll, sending the device_code:
POST /auth/token/ (no authentication){"device_code": "…"}| Status | Meaning |
|---|---|
200 |
Approved. Returns access_token, refresh_token, token_type, expires_in |
428 |
authorization_pending — keep polling |
410 |
The code expired. Start again |
404 |
Unknown device_code |
The user must be signed in to razorBridge and type the user_code on the
Authorize razorBridge CLI page. Approving creates their account, and its €10
signup credit, if they did not already have one.
Refresh, revoke, identity
Section titled “Refresh, revoke, identity”POST /auth/refresh/ {"refresh_token": "…"} → access_token, token_type, expires_inPOST /auth/revoke/ Bearer session token → {"revoked": true, "sessions_revoked": n}GET /auth/me/ Bearer session tokenPOST /auth/revoke/ revokes every session belonging to the caller, not just
the token presented — it is a sign-out-everywhere, and the right response to a
leaked token. A token that is already invalid gets {"revoked": false}.
GET /auth/me/ returns:
{ "user": {"id": 1, "email": "you@example.org", "name": "Your Name"}, "account": {"id": "…", "name": "Your Account", "balance_eur": "10.0000"}}Monetary values are strings throughout the API, to avoid float rounding.
Events
Section titled “Events”GET /events/
Section titled “GET /events/”Returns two lists, because your relationship to an event changes what you can see:
{ "participating": [{"id": "…", "name": "…", "slug": "…", "status": "running", "starts_at": "…", "ends_at": "…"}], "organizing": [{"id": "…", "name": "…", "slug": "…", "status": "scheduled", "starts_at": "…", "ends_at": "…", "join_code": "ABCD-1234"}]}join_code appears only for events you organize — it is the credential that lets
anyone join.
POST /events/join/
Section titled “POST /events/join/”{"code": "ABCD-1234"}Codes are upper-cased server-side, so case does not matter. Returns
{"joined": true, "event": {"id", "name", "slug"}}.
| Status | Meaning |
|---|---|
404 |
Event not found |
409 |
Registration closed, Already registered, or Event is full |
429 |
More than 20 join attempts from your IP in an hour |
GET /events/<slug>/
Section titled “GET /events/<slug>/”Detail for one event: id, name, slug, status, starts_at, ends_at,
gpu_type, is_organizer, and blade — null until yours exists, otherwise
{id, status, ip_address, gpu_type, time_remaining_seconds}. Organizers also get
join_code.
Returns 403 Not your event to anyone who is neither a participant nor the
organizing account, so events cannot be enumerated by slug.
POST /events/<slug>/extend/
Section titled “POST /events/<slug>/extend/”Organizer only. Extends a running event:
{"minutes": 60}minutes must be 30, 60 or 120. Returns {"extended": true, "new_ends_at", "estimated_cost_eur"}. A non-running event gives 409 Can only extend running events; someone else’s event gives 404.
POST /events/<slug>/roster/
Section titled “POST /events/<slug>/roster/”Organizer only. Adds participants in bulk:
{"participants": [{"email": "alice@university.edu", "name": "Alice"}, {"email": "bob@university.edu"}]}email is required; name defaults to the local part of the address. Returns
{"created": n, "skipped": n}. Entries are skipped when they duplicate an existing
participant, have no @, or would exceed the event’s participant cap — so
re-posting the same roster is safe.
Blades
Section titled “Blades”GET /blades/
Section titled “GET /blades/”{"blades": [{"id": "…", "event": "ML Workshop", "gpu_type": "gpu-l40sx1-48gb", "status": "running", "ip_address": "…", "started_at": "…"}]}GET /blades/<blade_id>/
Section titled “GET /blades/<blade_id>/”Adds region, runtime_hours and, for event blades, time_remaining_seconds
derived from the event’s end time. 403 Not your blade unless you are the assigned
participant or the organizing account.
GET /blades/<blade_id>/ssh/
Section titled “GET /blades/<blade_id>/ssh/”The connection credentials:
{"host": "…", "port": 2201, "username": "user01", "password": "…", "ssh_command": "ssh -p 2201 user01@…"}Only the assigned participant may read this — the organizing account may not.
409 Credentials not yet assigned means the blade is not ready. The first
successful read is recorded against the participant.
Credits
Section titled “Credits”GET /credits/
Section titled “GET /credits/”{ "balance_eur": "8.4300", "transactions": [ {"id": "…", "type": "debit", "amount_eur": "1.4444", "source": "consumption", "balance_after": "8.4300", "description": "…", "created_at": "…"} ]}Up to 50 entries, newest first. type is credit or debit; source values are
listed in Credits and billing.
GPU types
Section titled “GPU types”GET /gpu-types/
Section titled “GET /gpu-types/”Available GPUs with the current published pricing, cheapest first:
{"gpu_types": [{"slug": "gpu-l40sx1-48gb", "display_name": "L40S", "vram_gb": 48, "best_for": "Training & workshops", "hourly_usd": "…", "hourly_eur": "…"}]}hourly_eur is the authoritative rate, per GPU-hour, per blade. hourly_usd is a
convenience conversion at a fixed reference rate, not a price you can pay in
dollars.
This endpoint needs a session token, which also makes it the CLI’s connectivity probe: any HTTP response, including 401, proves the API is up.
Inference gate
Section titled “Inference gate”The gate is an OpenAI-compatible surface. For SDK usage, point the client’s
base_url at https://api.razorbridge.eu/v1/gate and let it append the rest.
POST /gate/chat/completions — gate key
Section titled “POST /gate/chat/completions — gate key”Body and response follow the OpenAI chat-completions shape; model is required.
The provider’s successful response is returned unchanged.
| Status | Meaning |
|---|---|
400 |
Invalid JSON, or model required |
401 |
Missing, unknown or revoked gate key |
402 |
Insufficient credits, or Key budget limit exceeded |
403 |
Model '…' not allowed for this key |
502 |
Inference failed upstream |
Streaming responses are not supported yet. Each successful call writes a usage record and deducts its cost from the account’s balance.
GET /gate/models — gate key
Section titled “GET /gate/models — gate key”{"models": [{"id": "klusai/fast", "owned_by": "razorbridge"}]}Model ids are aliases in the form klusai/<name>. The list reflects what the
platform routes today, so read it rather than caching a catalogue.
POST /gate/test — session token
Section titled “POST /gate/test — session token”A convenience wrapper for trying the gate before you have a key:
{"model": "klusai/fast", "message": "Hello"}model defaults to klusai/fast; message defaults to Hello; a full messages
array may be sent instead. Billed exactly like a normal call, and returns 402
with no balance.
GET /gate/usage — session token
Section titled “GET /gate/usage — session token”{"usage": [{"id": "…", "model": "klusai/fast", "prompt_tokens": 12, "completion_tokens": 48, "cost_eur": "0.000123", "created_at": "…"}]}Up to 50 entries, newest first.
GET /gate/keys and POST /gate/keys — session token
Section titled “GET /gate/keys and POST /gate/keys — session token”GET lists active keys: id, name, key_prefix, created_at, last_used_at.
POST creates one:
{"name": "Lab notebook", "allowed_models": ["klusai/fast"]}Returns 201 with the full key, which is shown only in this response —
afterwards only the prefix is retrievable. allowed_models is optional and is the
only way to scope a key to specific models; omit it for unrestricted access. A
per-key euro budget cap exists and is enforced, but cannot be set through this
endpoint.
POST /gate/keys/<key_id>/revoke — session token
Section titled “POST /gate/keys/<key_id>/revoke — session token”Returns {"revoked": true}, or 404 Key not found if the id does not belong to
your account.
Status codes
Section titled “Status codes”| Code | When |
|---|---|
400 |
Malformed JSON or a missing/invalid field |
401 |
Missing, wrong-type, expired or revoked credential |
402 |
Out of credits, or a key budget exhausted |
403 |
Authenticated, but not yours — someone else’s event, blade, or a disallowed model |
404 |
Not found, or deliberately indistinguishable from not-yours |
405 |
Wrong HTTP method |
409 |
State conflict — registration closed, event full, already registered, credentials not ready, event not running |
410 |
Device code expired |
428 |
Device authorization still pending — keep polling |
429 |
Rate limit exceeded |
502 |
Upstream inference failure |
Rate limits
Section titled “Rate limits”Applied per client IP, in a fixed window, to the unauthenticated and abuse-prone endpoints:
| Endpoint | Limit |
|---|---|
POST /auth/device/ |
10 per hour |
POST /auth/token/ |
20 per hour |
POST /auth/refresh/ |
30 per hour |
POST /events/join/ |
20 per hour |
Exceeding one returns 429 with Too many requests. Please slow down and try again later. Other endpoints are not rate-limited today; do not design against
that.
Related pages
Section titled “Related pages”- CLI reference — the client that wraps all of this.
- Inference gates — SDK examples and key scoping.
- Troubleshooting — what a given error usually means in practice.