Working with API Key and Consumer Metadata
Consumer metadata is a JSON object (up to 1 KB) attached to each API key consumer that is available at runtime on every authenticated request. It is the primary mechanism for passing tenant-specific data — such as a plan tier, customer ID, or organization — through the Zuplo gateway to your policies and backend.
This guide covers how to set, read, and forward metadata, and shows the most common patterns for using it in production.
Prerequisites
- A Zuplo project with the API Key Authentication policy configured on at least one route.
- At least one API key consumer. See Add API Key Authentication if you haven't created one yet.
How metadata flows
When a request arrives with a valid API key, the API Key Authentication policy
validates the key and populates request.user:
request.user.sub— the consumer's name (its unique identifier within the bucket).request.user.data— the consumer's metadata object.
Every policy and handler that runs after authentication can read these values. Metadata is set on the consumer, not on individual keys. All keys belonging to the same consumer share the same identity and metadata. For more on the consumer/key data model, see API Keys.
Metadata vs tags
Consumers support two kinds of key-value data. Mixing them up is a common mistake:
| Metadata | Tags | |
|---|---|---|
| Available at | Runtime (request.user.data) | Management API only |
| Purpose | Authorization, routing, rate limiting, metering | Filtering, querying, and organizing consumers |
| Size limit | 1 KB JSON object | Up to 5 key-value pairs per consumer |
| Sent to runtime | Yes — on every authenticated request | No — never reaches your policies or handlers |
| Set via | Portal, Developer API, on-login consumer creation | Portal, Developer API |
Tags are never sent to the runtime
If you store data in tags expecting to read it in a policy or handler, it will
not be there. Tags are for management operations only (for example, querying all
consumers on a specific plan via the Developer API). Use
metadata for anything your code needs at request time.
Setting metadata
You can set metadata when creating or updating a consumer through three channels.
Zuplo Portal
Navigate to Services → API Keys → Consumers in your project. When creating or editing a consumer, enter a JSON object in the Metadata field:
Code
Developer API
Use the Zuplo Developer API to create or update consumers
programmatically. Pass the metadata field in the request body:
Code
Replace $PROJECT_ID with your project's ID (visible in the Portal URL) and
$BUCKET_NAME with your API key bucket name (visible under Services → API
Keys in the Portal). $ZUPLO_API_KEY is a
Zuplo Developer API key.
To update metadata on an existing consumer, use a PATCH request to the same
endpoint with the consumer name appended. See the
API reference for the full endpoint documentation.
On-login consumer creation
If you use the Developer Portal (the self-serve portal where your API consumers manage their keys) with an identity provider like Auth0, you can create consumers automatically when a user signs in and set metadata from the identity token claims. See Create Consumer on Auth for the setup.
Reading metadata at runtime
After the API Key Authentication policy runs, access metadata in any downstream
policy or handler through request.user.data:
Code
For type safety, define an interface for your metadata shape and pass it under
the UserData key in the ZuploRequest type parameter. UserData is the key
name the generic expects; it maps to request.user.data. See
ZuploRequest for all available type
parameters.
Code
See RequestUser for the full type reference.
Forwarding metadata to your backend
A common pattern is to extract metadata values and forward them as HTTP headers so your backend receives tenant identity without needing to validate API keys itself. Create a custom inbound policy that runs after authentication:
-
Create the policy module
Create a new module file at
modules/forward-metadata.ts:modules/forward-metadata.tsAlways delete client-supplied copies of metadata headers before setting them from
request.user.data. If a metadata field is absent and you skip the delete, the client's spoofed value passes through to your backend. Your backend should trust these headers only because the gateway sets them. -
Register the policy
Add the policy to your
config/policies.json:Code -
Attach the policy to your route
Add
forward-metadatato the route's inbound policy array afterapi-key-inboundso thatrequest.useris populated before the policy runs. See Custom Code Inbound Policy for the full policy configuration reference.
Common patterns
Feature gating by plan
Check request.user.data.plan to allow or deny access to specific routes:
Code
Multi-tenant backend routing
Route each customer to a dedicated backend based on metadata. The following
policy sets context.custom.downstreamUrl, which the URL Rewrite handler reads
to determine the upstream destination. See
Route to Backends Based on User Identity
for the full setup including handler configuration.
Code
Dynamic rate limiting
Use metadata to apply different rate limits per plan tier. See
Dynamic Rate Limiting for the full
setup, which reads request.user.data to select the rate limit policy to apply.
Metadata scope and per-key behavior
Each consumer can have any number of API keys. All keys for a consumer share the
same identity (request.user.sub) and metadata (request.user.data). This is
by design — a consumer represents an identity, and metadata describes that
identity.
Per-key metadata is not supported. There is no way to attach different metadata to different keys under the same consumer. If you need different metadata for different keys — for example, different permissions or tenant identifiers — create separate consumers. Each consumer is an identity, so creating one consumer per identity that needs distinct metadata is the intended pattern.
For example, if a single organization has two teams that need different access
levels, create two consumers (acme-team-a and acme-team-b) with appropriate
metadata on each, rather than two keys on a single acme consumer.
If two keys need different runtime behavior, create separate consumers.
Visibility and security
Who can see consumer metadata
Understanding who has access to metadata is important when deciding what to store in it:
| Audience | Can see metadata? | Details |
|---|---|---|
| Your policies and handlers | Yes | Via request.user.data on every authenticated request |
| Zuplo Portal admins | Yes | Via Services → API Keys → Consumers in the project |
| Developer API callers | Yes | Via the Consumers API (requires a Zuplo API key) |
| Developer Portal key holders | No | Key managers can view, copy, and rotate their keys but do not see consumer metadata or tags |
What to store (and what not to store)
Metadata is available to your runtime code and to anyone with Developer API access. Follow these guidelines:
- Good for metadata: plan tier, customer ID, organization ID, feature flags, environment identifiers, external system references.
- Not for metadata: secrets, passwords, tokens, PII (email addresses, phone numbers), or any data that would cause harm if exposed through the Developer API.
Keep metadata small. The maximum size is 1 KB per consumer.
API key format
Zuplo API keys use the zpka_<random>_<checksum> format, enabling
leak detection via GitHub secret scanning. For
the full format breakdown and service limits, see
API Keys concepts and
API Key Service Limits.
Next steps
- API Key Authentication — how the
authentication policy sets
request.user - Route to Backends Based on User Identity — full guide on metadata-driven routing
- Dynamic Rate Limiting — apply different rate limits based on metadata
- Developer API — manage consumers and metadata programmatically
- API Key Best Practices — security and operational guidance