DEVELOPERS · v1.0 · 341 commands

One API. One binary.
Every auth primitive you need.

Bastionary exposes every capability — login, signup, MFA, sessions, users, teams, webhooks, licenses, payments, SMS, email — through a single POST /api/v1/execute endpoint. No SDK required. Copy the snippets on this page and ship.

1 · QUICKSTART

Your first request in 60 seconds

Everything goes through one endpoint: POST /api/v1/execute. The body names the command you want and its params. That's it.

cURL
JavaScript
Python
Rust
# 1. Sign up (or skip if you already have an account)
curl "https://bastionary.io/api/v1/execute" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "AUTH.SIGNUP",
    "params": {
      "email": "you@example.com",
      "password": "CorrectHorseBatteryStaple!",
      "display_name": "You"
    }
  }'

# 2. Log in → you get access_token + refresh_token
curl "https://bastionary.io/api/v1/execute" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "AUTH.LOGIN",
    "params": {
      "email": "you@example.com",
      "password": "CorrectHorseBatteryStaple!"
    }
  }'

# 3. Use the token on any protected command
curl "https://bastionary.io/api/v1/execute" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{"command":"USER.ME","params":{}}'
const BASE = "https://bastionary.io/api/v1/execute";

async function exec(command, params = {}, token = null) {
  const headers = { "Content-Type": "application/json" };
  if (token) headers.Authorization = `Bearer ${token}`;
  const r = await fetch(BASE, {
    method: "POST", headers,
    body: JSON.stringify({ command, params }),
  });
  const body = await r.json();
  if (!body.ok) throw new Error(body.error.message);
  return body.data;
}

// Log in, then call a protected command
const { access_token } = await exec("AUTH.LOGIN", {
  email: "you@example.com",
  password: "CorrectHorseBatteryStaple!",
});
const me = await exec("USER.ME", {}, access_token);
console.log(me);
import httpx

BASE = "https://bastionary.io/api/v1/execute"

def exec_cmd(command, params=None, token=None):
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    r = httpx.post(BASE, json={"command": command, "params": params or {}}, headers=headers)
    body = r.json()
    if not body["ok"]:
        raise RuntimeError(body["error"]["message"])
    return body["data"]

# Log in, then call a protected command
tokens = exec_cmd("AUTH.LOGIN", {
    "email": "you@example.com",
    "password": "CorrectHorseBatteryStaple!",
})
me = exec_cmd("USER.ME", token=tokens["access_token"])
print(me)
use serde_json::json;

async fn exec_cmd(
    client: &reqwest::Client,
    command: &str,
    params: serde_json::Value,
    token: Option<&str>,
) -> anyhow::Result<serde_json::Value> {
    let mut req = client
        .post("https://bastionary.io/api/v1/execute")
        .json(&json!({ "command": command, "params": params }));
    if let Some(t) = token {
        req = req.bearer_auth(t);
    }
    let body: serde_json::Value = req.send().await?.json().await?;
    if body["ok"] == false {
        anyhow::bail!("{}", body["error"]["message"]);
    }
    Ok(body["data"].clone())
}
2 · RESPONSE ENVELOPE

Every response has the same shape

Successes and failures share a single envelope. Your client code only needs to branch on ok.

Success
{
  "ok": true,
  "command": "USER.ME",
  "data": {
    "id": "usr_01HX…",
    "email": "you@example.com",
    "is_superadmin": false
  },
  "error": null
}
Failure
{
  "ok": false,
  "command": "AUTH.LOGIN",
  "data": null,
  "error": {
    "code": "AUTH_ERROR",
    "message": "Invalid credentials"
  }
}
AUTHENTICATION FLOW

Tokens, sessions, refresh

AUTH.LOGIN returns a short-lived access_token (15 min default) and a long-lived refresh_token. Put the access token in the Authorization: Bearer header. When it expires, call AUTH.REFRESH with the refresh token to get a new pair. Risk scoring, MFA, IP allowlists, and rate limits are all enforced by the Flow engine — you configure it visually, no code changes.

1. Login
AUTH.LOGIN
email + password → access & refresh tokens, MFA challenge if required.
2. Use
Authorization: Bearer …
Send the access token on every protected command. Superadmins bypass all permission checks.
3. Refresh
AUTH.REFRESH
When the access token expires (HTTP error TOKEN_EXPIRED), rotate it using the refresh token.

Authentication commands

Primitives for login, signup, MFA, password resets, and OAuth bridges.

AUTH.SIGNUPCreate account + tokens
AUTH.LOGINPassword login → tokens
AUTH.REFRESHRotate access token
AUTH.LOGOUTRevoke current session
AUTH.FORGOT_PASSWORDSend reset email
AUTH.RESET_PASSWORDConsume reset token
AUTH.MFA_SETUP_TOTPBegin TOTP enrollment
AUTH.MFA_VERIFYSubmit 2FA code

Users

CRUD, role changes, avatar uploads, profile fields. Superadmins get the full list; users get self-service.

USER.MEGet the caller's profile
USER.LISTPaginated user list
USER.UPDATEPatch display_name, avatar
USER.CHANGE_PASSWORDRotate password in-session
USER.DELETEHard delete account
USER.SET_ROLEAssign RBAC role

Sessions

Every active login is a session record. Revoke per-device or revoke-all on password change.

SESSION.LISTSessions for current user
SESSION.REVOKEKill one session by id
SESSION.REVOKE_ALLKill every session except current

Teams & RBAC

Multi-tenant workspaces with per-team roles and fine-grained permissions.

TEAM.CREATECreate workspace
TEAM.INVITEEmail invite with role
TEAM.LIST_MEMBERSRoster of a workspace
ROLE.LISTAvailable roles
PERMISSION.CHECKCan user X do Y on Z?

Flow engine

Model the auth pipeline visually at /flows. Every trigger (login, signup, refresh…) runs the enabled flow before the hardcoded path, so you can force MFA, deny by risk, or branch on IP allowlists without redeploying.

FLOW.LISTAll flows in the workspace
FLOW.GETOne flow by name
FLOW.CREATEDefine a new DAG
FLOW.UPDATEPatch nodes / edges
FLOW.DELETERemove a flow
FLOW.TESTDry-run against a context

Webhooks

HMAC-signed, at-least-once outbound deliveries with automatic retries and a dead-letter view.

WEBHOOK.CREATERegister endpoint + secret
WEBHOOK.LISTConfigured endpoints
WEBHOOK.DELIVERY_LISTDelivery attempts + status
WEBHOOK.REPLAYRetry a failed delivery

Licensing

Software licenses with feature flags, seat counts, expiration, and offline activation. Replaces Keygen / Cryptlex.

LICENSE.ISSUEMint a new license key
LICENSE.VALIDATEVerify + bump last_seen
LICENSE.REVOKEKill an active key
LICENSE.TRANSFERMove a key between machines

Payments

5 processors wired: Stripe, Paddle, Lemon Squeezy, Paypal, Mollie. One API, pick the vendor per product.

BILLING.CHECKOUT_CREATEHosted checkout session
BILLING.SUBSCRIPTION_LISTActive subscriptions
BILLING.INVOICE_LISTHistory with PDFs
BILLING.REFUNDFull or partial refund

All 341 commands

Searchable reference. Click any command to copy its fully-qualified name.

Loading registry from /api/v1/commands…

Error codes

Every failure returns one of these in error.code. They're stable — safe to branch on.

CodeMeaning
AUTH_ERRORInvalid credentials, expired session, or MFA required.
TOKEN_EXPIREDAccess token expired — call AUTH.REFRESH with your refresh token.
TOKEN_INVALIDToken signature failed or was rotated out.
PERMISSION_DENIEDAuthenticated, but the user lacks the required role/permission.
NOT_FOUNDReferenced resource does not exist or is not visible to the caller.
VALIDATION_ERRORRequest body failed schema validation. See message for details.
RATE_LIMITEDToo many requests. Back off per the Retry-After header.
FLOW_BLOCKEDYour configured flow denied this request — inspect flow.reason.
UNKNOWN_COMMANDCommand name not in the registry. Check spelling and case.
INTERNAL_ERRORBastionary misbehaved. Traces go to /status.

Rate limits

Defaults are generous; tune per-endpoint from the admin dashboard.

  • AUTH.LOGIN — 10 req / minute per IP (spike-guarded)
  • AUTH.FORGOT_PASSWORD — 3 req / hour per email
  • • General /api/v1/execute — 600 req / minute per token
  • • Webhook redelivery — 5 attempts, exponential backoff capped at 1h

SDKs

Official SDKs are thin wrappers — they speak the same command envelope. You never lose access to the underlying protocol.