> Markdown version of https://authpi.com/docs/guides/metadata/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# Metadata

Learn how to use metadata to store internal data on AuthPI resources.

**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:

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

### 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?

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

## Supported Resources

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 |

## 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:

| 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:**
- Objects (nested structures)
- Arrays

```json
// ✅ 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:

```json
{
  "config_json": "{\"nested\":\"object\"}"
}
```

## Using Metadata via API

### Setting Metadata on Creation

Include metadata when creating any resource:

```bash
# 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"
    }
  }'
```

```bash
# 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:

```bash
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`:

```bash
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:

```bash
curl https://api.authpi.com/v1/accounts/{account_id}/issuers/{issuer_id}/users/{user_id} \
  -u "{key_id}:{key_secret}"
```

```json
{
  "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:

```bash
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:

```json
// 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:

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

### Migration Tracking

Track data migration status:

```json
{
  "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:

```json
{
  "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:

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

### Audit Context

Record administrative context:

```json
{
  "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:

```json
// ✅ 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.

```markdown
## 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

```json
// ✅ 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:

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

### Organization Metadata

Stored with each organization:

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

### Membership Metadata

Stored per user-organization relationship:

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

### Client Metadata

Stored with each OAuth client:

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

### Webhook Metadata

Stored with each webhook:

```json
{
  "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](/docs/guides/webhooks) to track metadata changes:

```json
// user.updated webhook payload
{
  "type": "user.updated",
  "data": {
    "user_id": "usr_abc123",
    "updated_fields": ["metadata"]
  }
}
```

## Next Steps

- Learn about [Users](/docs/concepts/users) and their custom fields
- Understand [Organizations](/docs/concepts/organizations) and membership metadata
- Set up [Webhooks](/docs/guides/webhooks) to track metadata changes
- Explore the [Core API reference](/docs/reference/core-api/) for detailed endpoint documentation