Skip to main content

API Keys and Tokens

How API callers authenticate.

ApiKey​

An organization API key (opbxk_ prefix). The key is its own authenticatable principal, not a user: access is governed only by its per-resource permissions.

Table: api_keys
Model: App\Models\ApiKey
Tenant scoped: no

ColumnTypeNullableDefaultDescription
idbigint unsignedNoautoPrimary key
organization_idbigint unsignedNo-Tenant. Foreign key to organizations, on delete cascade
namevarchar(255)No-Key name
tokenvarchar(64)No-SHA-256 hash of the plain key; the plain key is shown once
last_used_attimestampYesnullLast use
created_bybigint unsignedYesnullUser who created the key. Foreign key to users, on delete set null
revoked_attimestampYesnullWhen the key was revoked; a revoked key is refused
created_attimestampYesnull
updated_attimestampYesnull

Indexes

  • api_keys_created_by_foreign: created_by
  • api_keys_organization_id_revoked_at_index: organization_id, revoked_at
  • api_keys_token_unique (unique): token

Hidden from serialization: token

Relationships

$apiKey->organization() → Organization (organization_id) [BelongsTo]
$apiKey->permissions() → ApiKeyPermission (api_key_id) [HasMany]
$apiKey->createdBy() → User (created_by) [BelongsTo]

Methods: levelForResource(), isRevoked(), getIsPlatformManagerAttribute(), getRoleAttribute(), hasRole(), isOwner(), isPBXAdmin(), isPBXUser(), isReporter(), isSupervisor()

Not tenant scoped: the key is resolved from the bearer token before a tenant context exists. For downstream code written for users, the key answers role checks as an Owner (role is owner), while isPBXAdmin(), isPBXUser(), isSupervisor() and isReporter() return false and is_platform_manager is false. Authorization is done by EnforceApiKeyScope.

ApiKeyPermission​

The access level an API key has on one resource.

Table: api_key_permissions
Model: App\Models\ApiKeyPermission
Tenant scoped: no

ColumnTypeNullableDefaultDescription
idbigint unsignedNoautoPrimary key
api_key_idbigint unsignedNo-API key. Foreign key to api_keys, on delete cascade
resourcevarchar(255)No-Resource slug (GrantableResource)
levelvarchar(255)No-read or write (cast to ApiKeyPermissionLevel) (cast to ApiKeyPermissionLevel)
created_attimestampYesnull
updated_attimestampYesnull

Indexes

  • api_key_permissions_api_key_id_resource_unique (unique): api_key_id, resource

Relationships

$apiKeyPermission->apiKey() → ApiKey (api_key_id) [BelongsTo]

Unique on (api_key_id, resource).

PersonalAccessToken​

Laravel Sanctum token used by the web app and by personal API tokens.

Table: personal_access_tokens
Model: App\Models\PersonalAccessToken (extends PersonalAccessToken)
Tenant scoped: no

ColumnTypeNullableDefaultDescription
idbigint unsignedNoautoPrimary key
tokenable_typevarchar(255)No-Owner model class
tokenable_idbigint unsignedNo-Owner ID
nametextNo-Token name
tokenvarchar(64)No-SHA-256 hash of the token (unique)
abilitiestextYesnullToken abilities
last_used_attimestampYesnullLast use
expires_attimestampYesnullExpiry
created_attimestampYesnull
updated_attimestampYesnull

Indexes

  • personal_access_tokens_expires_at_index: expires_at
  • personal_access_tokens_token_unique (unique): token
  • personal_access_tokens_tokenable_type_tokenable_id_index: tokenable_type, tokenable_id

Casts: abilities (json)
Hidden from serialization: token
Not mass assignable: tokenable_type, tokenable_id, last_used_at

Relationships

$personalAccessToken->tokenable() → PersonalAccessToken (tokenable_id) [MorphTo]

Extends Sanctum's model so tokenable() loads the user without OrganizationScope; otherwise Sanctum could not resolve the user before the tenant context exists. Each token expires at its own expires_at: 24 hours after a login, or 30 days after a Remember me login.