Guides

Metadata

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.

Why Use Metadata?

Metadata solves the problem of correlating AuthPI resources with your own systems without building separate mapping tables.

Common use cases:

  • Billing correlation: Link users or organizations to your billing system
  • External system IDs: Store Salesforce IDs, HubSpot contacts, or internal database references
  • Migration tracking: Mark resources that have been migrated from legacy systems
  • Feature flags: Track which users have access to beta features
  • Audit trails: Record who created or modified a resource and when
  • Regional routing: Tag resources with data residency requirements
  • Internal notes: Store context that helps your support team

Metadata vs. Custom Fields vs. Claims

AuthPI provides three ways to store additional data. Choosing the right one depends on who should see the data:

MetadataCustom FieldsClaims
VisibilityAdmin-onlyUser-visibleIn tokens
Who can setAdmins via APIAdmins via APIDerived from user/scopes
In API responsesYes (admin endpoints)Yes (all endpoints)No (only in tokens)
In tokensNeverNeverAlways
Available onAll resourcesUsers, OrganizationsTokens only

When to Use Each

Use metadata when:

  • The data is for internal use only
  • Users shouldn’t see or modify it
  • You’re correlating with external systems (billing, CRM, support)
  • You need audit or tracking information

Use custom fields when:

  • Users should be able to see the data
  • The data is part of the user’s profile (department, employee ID)
  • You want to extend the standard profile without tokens

Use claims when:

  • Your application needs the data in tokens for authorization decisions
  • External services (relying parties) need access to the data
  • The data affects what the user can do (roles, permissions, tiers)

Example: Employee Information

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?

FieldWhereWhy
employee_idCustom fieldUser sees it in their profile
departmentCustom fieldUser sees it; might filter by it
cost_centerMetadataInternal billing; user doesn’t need it
hr_system_idMetadataExternal system correlation; internal only

Supported Resources

Metadata is available on all major AuthPI resources:

ResourceUse Case Examples
UsersBilling IDs, CRM references, feature flags
OrganizationsTenant IDs, contract references, tier tracking
MembershipsRole assignment tracking, provisioning source
InvitationsCampaign tracking, referral sources
ClientsInternal app IDs, team ownership
WebhooksIntegration identifiers, monitoring tags
API KeysService identifiers, rotation tracking
Personal TokensPurpose tracking, automation identifiers
SessionsLogin context, risk scores
AccountsSales tracking, partnership flags

Technical Constraints

Key Format

  • Characters: Alphanumeric and underscore only (a-z, A-Z, 0-9, _)
  • Length: 1-100 characters
  • No spaces, hyphens, or special characters
✅ billing_account
✅ crm_id
✅ salesforce_contact_id
✅ isLegacyUser
✅ migration_batch_3

❌ billing-account     (hyphen)
❌ crm id              (space)
❌ salesforce.id       (dot)
❌ user@reference      (special char)

Value Types

Metadata values can be:

TypeExampleNotes
String"acct_12345"Max 500 characters
Number42 or 3.14Any valid JSON number
Booleantrue or false
NullnullExplicitly set to null

Not supported:

  • Objects (nested structures)
  • Arrays
// ✅ 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\"}"
}

Using Metadata via API

Setting Metadata on Creation

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"
    }
  }'

Updating Metadata

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
    }
  }'

Reading Metadata

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}"

Common Patterns

Billing System Correlation

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"
}

CRM Integration

Link AuthPI users to your CRM records:

{
  "salesforce_contact_id": "003xx000001234",
  "hubspot_contact_id": "12345678",
  "account_owner": "jane.smith",
  "lead_source": "webinar_2024_01"
}

Migration Tracking

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
}

Feature Flags

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"
}

Multi-Tenant Routing

Tag resources for multi-tenant architectures:

{
  "tenant_id": "tenant_acme",
  "data_region": "eu-west-1",
  "shard": "shard_03",
  "cluster": "prod_eu"
}

Audit Context

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"
}

Best Practices

Use Consistent Naming

Establish naming conventions for your team:

// ✅ Consistent snake_case
{
  "billing_account_id": "...",
  "salesforce_contact_id": "...",
  "created_by_admin": "..."
}

// ❌ Inconsistent naming
{
  "billingAccount": "...",
  "salesforce-contact": "...",
  "CreatedByAdmin": "..."
}

Document Your Metadata Fields

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 |

Keep Values Simple

Metadata is for simple values. If you need complex structures:

  1. Store a reference ID and keep details in your own database
  2. Serialize as JSON string (but avoid if possible)
  3. Split across multiple simple keys
// ✅ Simple reference
{
  "config_id": "cfg_12345"
}

// ❌ Complex nested data
{
  "config": "{\"rules\":[{\"type\":\"allow\",\"ip\":\"192.168.1.0/24\"}]}"
}

Don’t Store Sensitive Data

While metadata is admin-only, avoid storing:

  • Passwords or secrets
  • Personally identifiable information (PII) that doesn’t belong in AuthPI
  • Credit card numbers or financial data
  • Data subject to specific compliance requirements

If you must store sensitive references, use opaque IDs that require your systems to look up the actual data.

Use for Correlation, Not Business Logic

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.

Metadata in Different Contexts

User Metadata

Stored with each user, persists across sessions:

{
  "billing_account": "acct_12345",
  "signup_campaign": "product_hunt_2024",
  "referrer_user_id": "usr_xyz789"
}

Organization Metadata

Stored with each organization:

{
  "contract_id": "contract_2024_001",
  "account_manager": "jane.smith",
  "support_tier": "premium"
}

Membership Metadata

Stored per user-organization relationship:

{
  "provisioned_by": "scim",
  "role_assigned_at": "2024-01-15",
  "department_code": "ENG-001"
}

Client Metadata

Stored with each OAuth client:

{
  "team_owner": "platform_team",
  "environment": "production",
  "deployed_version": "2.3.1"
}

Webhook Metadata

Stored with each webhook:

{
  "integration_name": "zapier_sync",
  "monitored_by": "ops_team",
  "alerting_enabled": true
}

Security Considerations

Metadata Is Admin-Only

  • Only authenticated admin API calls can read or write metadata
  • End users cannot see metadata through user-facing APIs
  • Metadata is never included in tokens (access, ID, or refresh)
  • Client applications don’t have access unless you explicitly expose it

Access Control

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.

Audit Logging

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"]
  }
}

Next Steps