Skip to content

API reference

Authentication

SIWE nonce + JWT issuance.

Issue a single-use SIWE nonce

POST /auth/nonces

Public — no credentials

Responses

  • 201 Nonce created.
Terminal window
curl -X POST "https://api.rail0.xyz/auth/nonces"
Response 201
{
"nonce": "string",
"expires_at": "2026-10-01T12:00:00Z"
}

Verify a signed SIWE message and return a JWT

POST /auth

Public — no credentials

Request body

  • message string required

    EIP-4361 SIWE message text. Purpose-bound: its statement must be exactly "Sign in to RAIL0" — a proof signed for wallet registration (or with no statement) is refused with 422 siwe_purpose_mismatch, so a signature obtained for one flow can never be spent at the other.

  • signature string required

    Wallet signature over the SIWE message (0x…).

Responses

  • 201 SIWE verified; JWT issued.
  • 422 SIWE verification failed. The code field identifies the failing step: invalid_siwe, invalid_nonce, nonce_used, signer_mismatch, or one of the four binding failures siwe_domain_not_allowed / siwe_uri_mismatch / siwe_chain_mismatch / siwe_proof_expired (each names the value the caller sent, never the gateway's expected value). The message's chainId may be 1, or the id of any active chain of this environment's catalog; anything else is siwe_chain_mismatch. An address with no gateway account is NOT a failure — it yields an account-less token (201).
Terminal window
curl -X POST "https://api.rail0.xyz/auth" \
-H "Content-Type: application/json" \
-d '{
"message": "acme.example wants you to sign in with your Ethereum account:\n0x8ba1f109551bD432803012645Ac136ddd64DBA72\n\nSign in to RAIL0\n\nURI: https://acme.example\nVersion: 1\nChain ID: 84532\nNonce: 32891756\nIssued At: 2026-10-01T12:00:00Z",
"signature": "0x6b1f…9c1b"
}'
Response 201
{
"token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
"account_id": null,
"name": "Acme Store",
"expires_at": "2026-10-01T12:00:00Z"
}

End the current session

POST /auth/logout

Bearer JWT (Sign-In With Ethereum)

Revokes the token presented in the Authorization header, so it stops authenticating before its exp. PER TOKEN, not per address: signing out one device leaves the others signed in — revoking every session of an address is a different operation and is not this one. Requires the session it revokes, which is what keeps it from being a way to sign someone else out.

Best-effort by construction. The revocation list fails OPEN: if its store cannot be reached, a token cannot be known to be revoked and the request proceeds — one store outage must not sign out the whole platform. revoked in the response therefore reports whether the revocation was durably written; false means the token remains usable until exp, and the caller should treat its own copy as compromised.

Responses

  • 200 Session ended (see revoked).
  • 401 Authentication failed.
Terminal window
curl -X POST "https://api.rail0.xyz/auth/logout" \
-H "Authorization: Bearer $RAIL0_TOKEN"
Response 200
{
"revoked": false
}

Revoke every session of an address (fresh SIWE proof, not a session)

POST /auth/revoke_all

Public — no credentials

Writes an address-wide cutoff: every JWT issued to the address before it is refused with 401 sessions_revoked. Gated on a fresh SIWE proof carrying its own statement ("Sign out of RAIL0 everywhere") rather than on a session — an operator reacting to a leaked key holds the wallet, not the stolen token, and purpose-binding keeps a login signature from being replayed here. Self-revoking: the cutoff is now+1, so any token the caller holds dies too; sign in again afterwards. The revocation propagates within seconds (a short per-process cache pays the hot-path read), and the write is durable in Postgres — unlike the per-token denylist, this lever fails closed.

Request body

  • message string required

    EIP-4361 SIWE message signed by the address, carrying the revoke-all statement.

  • signature string required

    Signature over the SIWE message (0x…).

Responses

  • 200 Every session of the address issued before cutoff_at is revoked.
  • 422 The proof failed verification (same SIWE codes as POST /auth) or carries the wrong statement (siwe_purpose_mismatch).
Terminal window
curl -X POST "https://api.rail0.xyz/auth/revoke_all" \
-H "Content-Type: application/json" \
-d '{
"message": "acme.example wants you to sign in with your Ethereum account: …",
"signature": "0x6b1f…9c1b"
}'
Response 200
{
"revoked_all": true,
"cutoff_at": "2026-10-01T12:00:01Z"
}