Quickstart

A runnable walkthrough: create a user, grant them an app, check their access, then turn it off. Every call here is a real endpoint documented in the API Reference — this page just stitches them into one story. Getting Started is where to go for orientation; this page is where to go to see it work.

  1. 1. Create a user
  2. 2. Confirm they own nothing yet
  3. 3. Grant access to an app
  4. 4. Check what they can actually do
  5. 5. Assign a role inside the app
  6. 6. Turn it off
  7. What this skipped

All requests below assume the Conventions base URL and an admin bearer token, and send the API version in the Substratal-Version header (see Conventions → Versioning):

export SUBSTRATAL_API=https://api.substratalapps.com
export TOKEN=<an access token for a superadmin or support user>

1. Create a user

curl -s -X POST "$SUBSTRATAL_API/users" \
  -H "Substratal-Version: 1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "email": "jordan@example.com", "status": "invited" }'
{
  "id": "usr_01JAG3Z9X8QS3F6K2M4N5P6R7S",
  "email": "jordan@example.com",
  "status": "invited",
  "...": "..."
}

See Users. A default Profile, Settings, and the member platform role were created alongside this — nothing further to call for those.

2. Confirm they own nothing yet

curl -s "$SUBSTRATAL_API/users/usr_01JAG3Z9X8QS3F6K2M4N5P6R7S/entitlements" \
  -H "Substratal-Version: 1" \
  -H "Authorization: Bearer $TOKEN"
{ "data": [], "page": { "next_cursor": null, "has_more": false } }

3. Grant access to an app

In production, the Application developer’s own backend does this with its app-scoped API Key after its own billing confirms a payment (see Workflows → Purchase → access). Here, we grant directly, the way an admin comp or early-access grant would:

curl -s -X POST "$SUBSTRATAL_API/users/usr_01JAG3Z9X8QS3F6K2M4N5P6R7S/entitlements" \
  -H "Substratal-Version: 1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "application_id": "app_timetrack", "source": "admin_grant" }'
{
  "id": "ent_01JAGA1B2C3D4E5F6G7H8J9K01",
  "application_id": "app_timetrack",
  "status": "active",
  "source": "admin_grant",
  "...": "..."
}

See Entitlements. status: active is the whole point — this is the on/off switch, and it’s now on.

4. Check what they can actually do

curl -s "$SUBSTRATAL_API/users/usr_01JAG3Z9X8QS3F6K2M4N5P6R7S/apps/app_timetrack/effective-permissions" \
  -H "Substratal-Version: 1" \
  -H "Authorization: Bearer $TOKEN"
{
  "user_id": "usr_01JAG3Z9X8QS3F6K2M4N5P6R7S",
  "application_id": "app_timetrack",
  "allowed": false,
  "user_status": "invited",
  "entitlement_status": "active",
  "access_paths": [ { "source": "admin_grant", "entitlement_id": "ent_01JAGA1B2C3D4E5F6G7H8J9K01", "status": "active" } ],
  "roles": [],
  "effective_permissions": [],
  "computed_at": "2026-10-05T12:00:00Z"
}

allowed is still false: the user is invited, and access requires an active user. Activate them so the rest of the walkthrough works (outside a quickstart, they’d accept the emailed invitation instead):

curl -s -X PATCH "$SUBSTRATAL_API/users/usr_01JAG3Z9X8QS3F6K2M4N5P6R7S" \
  -H "Substratal-Version: 1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "active" }'

Check again: allowed: true, entitlement_status: "active".

effective_permissions is still empty, because no Role has been assigned yet (and app_timetrack here has no default_app_role) — owning an app and having a role inside it are different axes. See Access Control.

5. Assign a role inside the app

curl -s -X POST "$SUBSTRATAL_API/users/usr_01JAG3Z9X8QS3F6K2M4N5P6R7S/roles/role_timetrack_admin" \
  -H "Substratal-Version: 1" \
  -H "Authorization: Bearer $TOKEN"

Repeat step 4 and effective_permissions now includes whatever role_timetrack_admin grants (e.g. app.timetrack.export).

6. Turn it off

curl -s -X PATCH "$SUBSTRATAL_API/entitlements/ent_01JAGA1B2C3D4E5F6G7H8J9K01" \
  -H "Substratal-Version: 1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "disabled", "disabled_reason": "quickstart_demo" }'

Repeat step 4 again: allowed is false, entitlement_status is now disabled, and effective_permissions is back to [] — the Role assignment from step 5 is still there underneath, untouched, ready the moment the entitlement is re-enabled. See Workflows → Admin turns an app off for a user.

What this skipped

Login/token issuance (Auth), the app-launch JWT and introspection that a downstream app calls rather than an admin (Trust Model), and webhook delivery (Webhooks) — all real, all documented, just not needed to see the core access model work end to end.