Enforcer v3 docs / Enforcer Quickstart
Get going
Quickstart
The live sandbox is open, no setup needed. Base URL is https://api.instruxi.dev/api/v1/enforcer and the sandbox tenant code is DLXZ-57TL-FS75. Watch the 30 second walkthrough, run it yourself right here, or do it by hand below.
Walk into the front desk
A real sign in, a real identity check, and a real access gate, shown as a little world. Sign in, prove who you are with a sandbox ID check, and watch the locked room open.
This is the front desk for your app. Sign in to walk through.
Real sign-ins here are logged so the Enforcer team can follow up. No marketing spam.
Access granted. The room is open.
Sign in to the sandbox
Real email, real token. Ask for a one time code, exchange it for a session token, then call the API with that token.
curl -X POST "https://api.instruxi.dev/api/v1/enforcer/auth/otp/request" \ -H "Content-Type: application/json" \ -d '{ "email": "you@example.com", "tenant_code": "DLXZ-57TL-FS75" }' # a 6 digit OTP is emailed to you, valid for 10 minutes
Response
{ "success": true, "message": "code sent" }curl -X POST "https://api.instruxi.dev/api/v1/enforcer/auth/login" \ -H "Content-Type: application/json" \ -d '{ "provider": "email_otp", "email": "you@example.com", "otp": "123456", "tenant_code": "DLXZ-57TL-FS75" }' # returns a token plus a refresh_token. Honour expires_at; this sandbox issues ~15 min
Response
{
"success": true,
"data": {
"token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi...",
"account": { "id": "acc_01J...", "email": "you@example.com", "tenant_id": "ten_01J...", "role": "user" }
}
}# the login response gives you data.token and data.account.id # send the token on every call curl "https://api.instruxi.dev/api/v1/enforcer/auth/me" \ -H "Authorization: Bearer <token>"
If you would rather hold a server credential than a session token, POST /api-keys with {"name":"..."} and the bearer from the previous step mints one. The plaintext key is in that response and nowhere else, so capture it there.
See the gate flip
Groups are how a capability gets unlocked. Create one, add yourself, and you are through the gate. In the sandbox you do it by hand to watch it work. In production a KYC pass or your own policy adds people to the group automatically.
# the sandbox is shared, so pick a name and slug that are your own curl -X POST "https://api.instruxi.dev/api/v1/enforcer/groups" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "Verified yourname", "slug": "verified-yourname", "description": "passed the check", "is_public": false }' # response: data.id is your new group id
curl -X POST "https://api.instruxi.dev/api/v1/enforcer/groups/<group_id>/members" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "account_id": "<your account id from login>" }'
Response
{ "success": true }curl "https://api.instruxi.dev/api/v1/enforcer/groups/<group_id>/members" \ -H "Authorization: Bearer <token>" # your account is in the member list. that membership is the gate.
That is the whole idea. Group membership flips a capability from blocked to allowed with no code change on the route. Swap the manual add for a KYC pass and you have the production flow.
curl -X POST "https://api.instruxi.dev/api/v1/enforcer/auth/refresh" \ -H "Content-Type: application/json" \ -d '{ "refresh_token": "<refresh_token>" }'
Response
{ "success": true, "data": { "token": "eyJhbGciOi...", "refresh_token": "eyJhbGciOi..." } }Two ways to build on it:
The fastest path
Inside an AI coding tool such as Cursor, Claude Code, Lovable, or Windsurf, you describe the app you want and the Enforcer connector provisions a real tenant and scaffolds a working app wired to it.
build me a banking app with login and KYC
The connector creates a tenant, mints the keys, wires up the auth methods you asked for, and leaves you with code that already talks to Enforcer. From there you keep building in plain language or drop down to the API below.
The connector is the front door for new builds. When you need exact control, the by hand path uses the same endpoints under the hood.
The sandbox above signs you into a shared tenant. To stand up your own tenant and gate an action, here is the full sequence. Self serve tenant creation is gated per account, so if the first call returns missing_token or a 403 that is the gate, not a mistake on your part. Your base URL is https://api.instruxi.dev (live now). Replace the placeholder values as you go. Values like $TENANT_CODE, $GROUP_ID and $ACCOUNT_ID come from the responses of earlier steps, noted inline below. Single quoted JSON does not expand shell variables, so substitute the real values by hand when you run these.
-
Create your tenant, no human in the loop
Self serve tenant creation makes the caller the tenant admin and hands back the new tenant. The response carries the tenant (note its
idandcode) and your tenant admin session token. Use that token as$ADMIN_JWTand the tenant code as$TENANT_CODEbelow. The exact field names are in the worked reference, generated from the live spec.requestcurl -X POST "$ENFORCER_BASE_URL/api/v1/enforcer/tenants/self-serve" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Pay", "admin_email": "you@acme.com" }' # response: the new tenant (id, code) plus your tenant-admin session token
-
Mint a server API key
Use the tenant admin token from the step above as the bearer. The plaintext key is shown once, store it now.
requestcurl -X POST "$ENFORCER_BASE_URL/api/v1/enforcer/api-keys" \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{ "name": "server-key" }'
Response
{ "success": true, "data": { "key": "ek_live_9f3c...a71b", "api_key": { "id": "key_01J...", "name": "server", "key_prefix": "ek_live_9f3c" } } } -
Register a user and request an OTP
Register the account, then request an email OTP scoped to your tenant. In development the OTP is returned to you so you can script the flow.
request# register curl -X POST "$ENFORCER_BASE_URL/api/v1/enforcer/auth/register" \ -H "Content-Type: application/json" \ -d '{ "provider": "email_otp", "email": "user@acme.com" }' # request the OTP curl -X POST "$ENFORCER_BASE_URL/api/v1/enforcer/auth/otp/request" \ -H "Content-Type: application/json" \ -d '{ "email": "user@acme.com", "tenant_code": "$TENANT_CODE" }'
-
Log in to get a user JWT
Exchange the OTP for a user bearer token plus a refresh token.
requestcurl -X POST "$ENFORCER_BASE_URL/api/v1/enforcer/auth/login" \ -H "Content-Type: application/json" \ -d '{ "provider": "email_otp", "email": "user@acme.com", "otp": "123456", "tenant_code": "$TENANT_CODE" }' # response includes: token, refresh_token, expires_at, account
-
Add the user to the gating group
The group is the capability gate. Membership is what unlocks the route the group protects. List your tenant's groups to find the one you want to gate on (its
idis$GROUP_ID), then add the account ($ACCOUNT_IDfrom the login response above). The API key goes in theX-API-Keyheader. Groups are provisioned with your tenant, confirm with your Enforcer contact how your gating groups are seeded.request# list this tenant's groups, take the id of the one you gate on curl "$ENFORCER_BASE_URL/api/v1/enforcer/groups" \ -H "X-API-Key: $API_KEY" # add the account to it -> capability now unlocked curl -X POST "$ENFORCER_BASE_URL/api/v1/enforcer/groups/$GROUP_ID/members" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "$ACCOUNT_ID" }'
That last step is the whole point. The user existed and could log in, but the gated action was forbidden. Adding them to the group flips that to allowed, with no code change on the route. Swap the manual add for the KYC module reporting a pass and you have the production verification flow.