Anonymous Access Policy
Gates can offer degraded, read-only access to unauthenticated agents, serving as both enforcer and onboarding point. Invalid passports are always denied; they never fall back to anonymous access.
Anti-downgrade invariant, critical security rule
An agent that presents an expired, revoked, or otherwise invalid passport is always DENIED, it never falls back to anonymous access. This prevents attackers from intentionally using bad credentials to bypass constraints. Anonymous access only applies to agents that present no passport at all.
What is anonymous access?
Anonymous access allows a gate to serve unauthenticated agents, those that don't present a passport, with a limited, rate-limited, read-only subset of your API. This is useful for:
- Service discovery and capability exploration before an agent gets a passport
- Public read-only endpoints (search, catalog listing, documentation)
- Onboarding flows where agents discover your service and then request credentials
Every anonymous response can include an upgrade_message and upgrade_url, so your gate doubles as an onboarding funnel for new agents.
Configuration
Configure anonymous access via the API or dashboard (Dashboard → Gates → [gate] → Anonymous Access).
curl -X PUT \
https://modei.ai/api/v1/gates/gate_my-api/anonymous-policy \
-H "Authorization: Bearer mod_your_key" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"allowed_actions": ["api:search", "api:catalog"],
"read_only": true,
"rate_limit_per_minute": 5,
"rate_limit_per_hour": 50,
"upgrade_message": "Get a passport for full access, 10x rate limits, billing API, and service-level agreement (SLA) guarantees.",
"upgrade_url": "https://myapi.com/get-access"
}'Policy fields
| Field | Type | Description |
|---|---|---|
| enabled | boolean | Toggle anonymous access on/off. Default: false. |
| allowed_actions | string[] | Which actions anonymous agents can request. Must be a subset of your catalog. |
| read_only | boolean | If true, only read-only actions are permitted. Extra safety layer. |
| rate_limit_per_minute | number | Max requests per minute from any single anonymous agent. |
| rate_limit_per_hour | number | Max requests per hour from any single anonymous agent. |
| upgrade_message | string | Message included in every anonymous response explaining how to get full access. |
| upgrade_url | string | URL where the agent can obtain a passport. |
MCP tools
set_anonymous_policy
Configure anonymous access for a gate.
{
"gate_id": "gate_my-api",
"enabled": true,
"allowed_actions": ["api:search"],
"rate_limit_per_minute": 5,
"upgrade_url": "https://myapi.com/signup"
}get_anonymous_policy
Read the current anonymous access configuration.
{
"gate_id": "gate_my-api"
}Example Flow: Graceful Onboarding
This is Use Case 7 from the feature inventory, an unknown agent discovering and upgrading to authenticated access.
Unknown agent queries without passport
Calls POST /api/gates/gate_my-api/check with no passport_id. Gate checks anonymous policy: action "api:search" is in allowed_actions, rate limit not exceeded → allow.
Response includes upgrade prompt
Every anonymous response includes: { "upgrade_message": "Get a passport for full access...", "upgrade_url": "https://myapi.com/signup" }
Agent attempts to use an expired passport
Presents a passport that expired last week. Gate sees a passport → runs full verification → passport is expired → decision: block. The anti-downgrade invariant prevents fallback to anonymous.
Agent's operator gets a passport
Operator signs up, creates an issuer, gets a passport with api:search and api:export permissions. Agent switches to authenticated access.
Agent now has full access
Authenticated calls have 10x rate limits, access to api:export, and full billing/metering. Anonymous access is no longer used.
Anti-Downgrade: The Security Invariant in Detail
The gate follows this strict logic for every request:
def check_gate(passport_id, action, target):
if passport_id is None:
# No passport presented
if anonymous_policy.enabled and action in anonymous_policy.allowed_actions:
return check_anonymous_rate_limits(action) # allow or block
return block("no_passport")
# Passport was presented, run FULL verification
passport = load_passport(passport_id)
if passport is None:
return block("passport_not_found") # Never anonymous fallback
if passport.revoked:
return block("passport_revoked") # Never anonymous fallback
if passport.expires_at < now():
return block("passport_expired") # Never anonymous fallback
# ... signature check, catalog pin check, permission check ...
return allow if has_permission(passport, action) else block("no_permission")The key invariant: any presented passport (even an invalid one) routes to the full verification path, never to the anonymous path. Only requests with no passport can use anonymous access.
Rate Limiting for Anonymous Agents
Anonymous rate limits are tracked per IP address or per agent identifier (if provided). The system maintains a rolling window counter in the anonymous_access_log table.
When an anonymous agent exceeds its rate limit, the gate returns:
{
"decision": "block",
"reason": "anonymous_rate_limit_exceeded",
"upgrade_message": "Get a passport for full access, 10x rate limits",
"upgrade_url": "https://myapi.com/get-access",
"retry_after": 60
}Related
- Gates, Security profiles, verification logic, configuration.
- Enforcement Layer, How constraints are evaluated for authenticated agents.
- Gates API, Full anonymous-policy API reference.