Learn how to use metadata to store internal data on AuthPI resources.
Last updated 2026-06-11
Metadata is a key-value store attached to every resource in AuthPI. It lets you store arbitrary internal data—like billing account IDs, external system references, or feature flags—without modifying AuthPI’s schema.
Metadata is admin-only. It’s never exposed to end users, never included in tokens, and can only be set or read through authenticated API calls. This makes it safe for storing internal information that shouldn’t be visible to your users.
Metadata solves the problem of correlating AuthPI resources with your own systems without building separate mapping tables.
Common use cases:
AuthPI provides three ways to store additional data. Choosing the right one depends on who should see the data:
| Metadata | Custom Fields | Claims | |
|---|---|---|---|
| Visibility | Admin-only | User-visible | In tokens |
| Who can set | Admins via API | Admins via API | Derived from user/scopes |
| In API responses | Yes (admin endpoints) | Yes (all endpoints) | No (only in tokens) |
| In tokens | Never | Never | Always |
| Available on | All resources | Users, Organizations | Tokens only |
Use metadata when:
Use custom fields when:
Use claims when:
Consider storing employee data for a B2B application:
employee_id: "EMP12345"
department: "Engineering"
cost_center: "CC-4501"
hr_system_id: "workday_98765"
Where should each go?
| Field | Where | Why |
|---|---|---|
employee_id | Custom field | User sees it in their profile |
department | Custom field | User sees it; might filter by it |
cost_center | Metadata | Internal billing; user doesn’t need it |
hr_system_id | Metadata | External system correlation; internal only |
Metadata is available on all major AuthPI resources:
| Resource | Use Case Examples |
|---|---|
| Users | Billing IDs, CRM references, feature flags |
| Organizations | Tenant IDs, contract references, tier tracking |
| Memberships | Role assignment tracking, provisioning source |
| Invitations | Campaign tracking, referral sources |
| Clients | Internal app IDs, team ownership |
| Webhooks | Integration identifiers, monitoring tags |
| API Keys | Service identifiers, rotation tracking |
| Personal Tokens | Purpose tracking, automation identifiers |
| Sessions | Login context, risk scores |
| Accounts | Sales tracking, partnership flags |
a-z, A-Z, 0-9, _)✅ billing_account
✅ crm_id
✅ salesforce_contact_id
✅ isLegacyUser
✅ migration_batch_3
❌ billing-account (hyphen)
❌ crm id (space)
❌ salesforce.id (dot)
❌ user@reference (special char)
Metadata values can be:
| Type | Example | Notes |
|---|---|---|
| String | "acct_12345" | Max 500 characters |
| Number | 42 or 3.14 | Any valid JSON number |
| Boolean | true or false | |
| Null | null | Explicitly set to null |
Not supported:
// ✅ Valid metadata
{
"billing_account": "acct_12345",
"tier": "enterprise",
"seats_purchased": 50,
"is_trial": false,
"migrated_at": null
}
// ❌ Invalid metadata
{
"config": { "nested": "object" },
"tags": ["tag1", "tag2"]
}
If you need to store complex data, serialize it as a JSON string:
{
"config_json": "{\"nested\":\"object\"}"
}
Include metadata when creating any resource:
# Create a user with metadata
curl -X POST https://api.authpi.com/v1/accounts/{account_id}/issuers/{issuer_id}/users \
-u "{key_id}:{key_secret}" \
-H "Content-Type: application/json" \
-d '{
"username": "john@example.com",
"username_type": "email",
"metadata": {
"billing_account": "acct_12345",
"salesforce_contact": "003xx000001234",
"onboarded_by": "sales_rep_jane"
}
}'
# Create an organization with metadata
curl -X POST https://api.authpi.com/v1/accounts/{account_id}/issuers/{issuer_id}/organizations \
-u "{key_id}:{key_secret}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation",
"org_type": "business",
"metadata": {
"contract_id": "contract_2024_001",
"tier": "enterprise",
"data_region": "eu-west-1"
}
}'
Use PATCH to update metadata. New keys are added; existing keys are updated:
curl -X PATCH https://api.authpi.com/v1/accounts/{account_id}/issuers/{issuer_id}/users/{user_id} \
-u "{key_id}:{key_secret}" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"billing_account": "acct_67890",
"upgraded_at": "2024-01-15"
}
}'
To remove a metadata key, set it to null:
curl -X PATCH https://api.authpi.com/v1/accounts/{account_id}/issuers/{issuer_id}/users/{user_id} \
-u "{key_id}:{key_secret}" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"temporary_flag": null
}
}'
Metadata is included in GET responses:
curl https://api.authpi.com/v1/accounts/{account_id}/issuers/{issuer_id}/users/{user_id} \
-u "{key_id}:{key_secret}"
{
"id": "usr_abc123",
"username": "john@example.com",
"status": "active",
"metadata": {
"billing_account": "acct_67890",
"salesforce_contact": "003xx000001234",
"onboarded_by": "sales_rep_jane",
"upgraded_at": "2024-01-15"
},
"created_at": 1705330953123
}
Metadata is also included when listing resources:
curl https://api.authpi.com/v1/accounts/{account_id}/issuers/{issuer_id}/users \
-u "{key_id}:{key_secret}"
Store billing system references on users and organizations:
// User metadata
{
"stripe_customer_id": "cus_abc123",
"billing_email": "billing@acme.com",
"payment_method_added": true
}
// Organization metadata
{
"stripe_subscription_id": "sub_xyz789",
"plan": "enterprise",
"billing_cycle": "annual",
"contract_end_date": "2025-12-31"
}
Link AuthPI users to your CRM records:
{
"salesforce_contact_id": "003xx000001234",
"hubspot_contact_id": "12345678",
"account_owner": "jane.smith",
"lead_source": "webinar_2024_01"
}
Track data migration status:
{
"migrated_from": "legacy_auth_system",
"migration_batch": "batch_2024_03",
"migrated_at": "2024-03-15T10:30:00Z",
"legacy_user_id": "old_12345",
"migration_verified": true
}
Control feature access at the user or organization level:
{
"beta_features_enabled": true,
"feature_new_dashboard": true,
"feature_api_v3": false,
"enrolled_experiments": "exp_123,exp_456"
}
Tag resources for multi-tenant architectures:
{
"tenant_id": "tenant_acme",
"data_region": "eu-west-1",
"shard": "shard_03",
"cluster": "prod_eu"
}
Record administrative context:
{
"created_by_admin": "admin_user_456",
"creation_reason": "customer_request",
"last_reviewed_at": "2024-02-01",
"reviewed_by": "compliance_team",
"notes": "High-value enterprise customer"
}
Establish naming conventions for your team:
// ✅ Consistent snake_case
{
"billing_account_id": "...",
"salesforce_contact_id": "...",
"created_by_admin": "..."
}
// ❌ Inconsistent naming
{
"billingAccount": "...",
"salesforce-contact": "...",
"CreatedByAdmin": "..."
}
Create internal documentation listing what metadata fields you use, their purpose, and valid values. This prevents field sprawl and inconsistency.
## User Metadata Fields
| Field | Type | Description | Example |
|-------|------|-------------|---------|
| billing_account_id | string | Stripe customer ID | cus_abc123 |
| tier | string | Subscription tier | free, pro, enterprise |
| onboarded_by | string | Sales rep username | jane.smith |
Metadata is for simple values. If you need complex structures:
// ✅ Simple reference
{
"config_id": "cfg_12345"
}
// ❌ Complex nested data
{
"config": "{\"rules\":[{\"type\":\"allow\",\"ip\":\"192.168.1.0/24\"}]}"
}
While metadata is admin-only, avoid storing:
If you must store sensitive references, use opaque IDs that require your systems to look up the actual data.
Metadata is best for correlation and tracking. Avoid building complex business logic around metadata queries. If you need to frequently filter or query by certain attributes, consider whether they should be proper fields instead.
Stored with each user, persists across sessions:
{
"billing_account": "acct_12345",
"signup_campaign": "product_hunt_2024",
"referrer_user_id": "usr_xyz789"
}
Stored with each organization:
{
"contract_id": "contract_2024_001",
"account_manager": "jane.smith",
"support_tier": "premium"
}
Stored per user-organization relationship:
{
"provisioned_by": "scim",
"role_assigned_at": "2024-01-15",
"department_code": "ENG-001"
}
Stored with each OAuth client:
{
"team_owner": "platform_team",
"environment": "production",
"deployed_version": "2.3.1"
}
Stored with each webhook:
{
"integration_name": "zapier_sync",
"monitored_by": "ops_team",
"alerting_enabled": true
}
The API key or user session making the request must have appropriate permissions to read/write the resource. Metadata follows the same access control as the parent resource.
Changes to metadata are logged as part of resource updates. Use webhooks to track metadata changes:
// user.updated webhook payload
{
"type": "user.updated",
"data": {
"user_id": "usr_abc123",
"updated_fields": ["metadata"]
}
}