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
| Column | Type | Nullable | Default | Description |
|---|---|---|---|---|
id | bigint unsigned | No | auto | Primary key |
organization_id | bigint unsigned | No | - | Tenant. Foreign key to organizations, on delete cascade |
name | varchar(255) | No | - | Key name |
token | varchar(64) | No | - | SHA-256 hash of the plain key; the plain key is shown once |
last_used_at | timestamp | Yes | null | Last use |
created_by | bigint unsigned | Yes | null | User who created the key. Foreign key to users, on delete set null |
revoked_at | timestamp | Yes | null | When the key was revoked; a revoked key is refused |
created_at | timestamp | Yes | null | |
updated_at | timestamp | Yes | null |
Indexes
api_keys_created_by_foreign:created_byapi_keys_organization_id_revoked_at_index:organization_id,revoked_atapi_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
| Column | Type | Nullable | Default | Description |
|---|---|---|---|---|
id | bigint unsigned | No | auto | Primary key |
api_key_id | bigint unsigned | No | - | API key. Foreign key to api_keys, on delete cascade |
resource | varchar(255) | No | - | Resource slug (GrantableResource) |
level | varchar(255) | No | - | read or write (cast to ApiKeyPermissionLevel) (cast to ApiKeyPermissionLevel) |
created_at | timestamp | Yes | null | |
updated_at | timestamp | Yes | null |
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
| Column | Type | Nullable | Default | Description |
|---|---|---|---|---|
id | bigint unsigned | No | auto | Primary key |
tokenable_type | varchar(255) | No | - | Owner model class |
tokenable_id | bigint unsigned | No | - | Owner ID |
name | text | No | - | Token name |
token | varchar(64) | No | - | SHA-256 hash of the token (unique) |
abilities | text | Yes | null | Token abilities |
last_used_at | timestamp | Yes | null | Last use |
expires_at | timestamp | Yes | null | Expiry |
created_at | timestamp | Yes | null | |
updated_at | timestamp | Yes | null |
Indexes
personal_access_tokens_expires_at_index:expires_atpersonal_access_tokens_token_unique(unique):tokenpersonal_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.