Summary
I would like to propose adding a small origin model to the anonymous-start registration flow in auth.md.
The important scope is narrow: this proposal is only about the anonymous flow where an agent can receive a usable pre-claim credential before a human has taken ownership. It is not a proposal for identity_assertion with ID-JAG, identity_assertion with verified_email, agent-provider registration, provider-driven revocation, or any other verified identity flow.
The reason is simple: SaaS services usually need to give new users some free value before asking them to pay, approve, or fully commit. That can be a trial period, credits, a limited number of tool calls, read-only access, storage, compute, or some other service-specific allowance.
With the anonymous auth.md flow, an agent can start that flow before a human is present. That is useful for product-led onboarding. The agent can try the service, see whether it is useful, and later ask a human to claim the account.
The same thing creates an abuse-control problem. Software can create accounts and consume free resources much faster than a human. If the protocol makes anonymous account creation easy, services need a common way to meter that free access without relying only on blunt IP limits.
Scope
The current README describes three registration flows on /agent/auth: ID-JAG identity assertion, verified-email identity assertion, and anonymous registration with OTP claim.
This issue only concerns the third flow: anonymous registration with OTP claim.
That flow is different because the service immediately creates an agent principal, issues a scoped API key, returns a claim token, and lets the agent operate with pre-claim scopes. The service may later upgrade the same key after a human claim ceremony.
The other two flows already have a stronger identity axis. In the ID-JAG path, a trusted agent provider asserts identity. In the verified-email path, the service sends email before issuing the credential. Those flows may still need rate limits, but I do not think this proposal should change their request shape.
Current anonymous auth.md shape
Today the anonymous request is intentionally small:
{
"type": "anonymous",
"requested_credential_type": "api_key"
}
That is enough for the service to create an anonymous agent account, return a credential, expose pre-claim permissions, and provide the claim information that lets a human take ownership later.
What it does not provide is a standard way to describe where the anonymous registration came from.
That missing origin becomes important once the service grants real trial value to the anonymous account.
What I think is missing
A service may want to limit anonymous registrations by more than one axis.
A simple IP cap is useful, but it is not enough. Many honest users can share one IP address, and abusive traffic can rotate IP addresses.
A simple final-account cap is not enough either. If every anonymous registration creates a new final account or workspace, the cap resets with every new account.
There is also an isolation problem. Two similar agents on the same machine, or two threads of the same app, can produce identical request metadata. If the service derives the final account directly from that metadata, two different credentials can end up pointing at the same unclaimed account.
So I think the anonymous flow needs to distinguish between two things:
- a shared origin key that can be used for abuse limits
- the final account or workspace ID that the issued credential can access
Those should not be the same thing.
Proposed anonymous origin values
The existing anonymous request could stay valid, with three optional caller-supplied fields added to the anonymous request shape:
{
"type": "anonymous",
"requested_credential_type": "api_key",
"channel": "application",
"source": "application-a",
"user": "123"
}
channel describes what kind of path brought the request. For example: application, agent, browser, system, or unknown.
source identifies the app, agent, browser, or system inside that channel.
user identifies the source-local user or subject when that is known. If it is not known, the service can treat it as anonymous.
There is also a fourth origin value, which I would call connection, but it should not be submitted by the agent. The service should derive it from trusted request metadata: the observed IP address, a trusted edge header, a gateway value, a device signal, or another connection signal the service controls.
That connection value should be hashed before it is stored.
How the limits would work
The service can normalize the caller-supplied anonymous origin values and combine them with a hashed service-derived connection key:
channel:source:user:connectionKey
Example:
application:application-a:123:hash:ip-203-0-113-10
This is the shared limit key. It is used for caps and analysis. It is not the final account ID.
The service can then allocate a suffix under that shared key:
shared limit key: application:application-a:123:hash:ip-203-0-113-10
suffix: 0
The final account ID should still be generated in the service's normal format. For example, a SaaS app may use UUIDv7 or another regular database/account ID format:
final account id: 018f6c83-4c4a-7000-8000-000000000001
If the same anonymous origin registers again, it keeps the same shared limit key, receives the next suffix, and receives another generated account ID:
shared limit key: application:application-a:123:hash:ip-203-0-113-10
suffix: 1
final account id: 018f6c83-4c4a-7000-8000-000000000002
Both accounts count against the same anonymous origin, but the credentials do not point at the same unclaimed account.
What this enables
With this shape, a service can limit anonymous-start registrations by:
- channel
- source
- user
- connection key
- shared limit key
- total count
It can also apply different policies before and after claim.
Before claim, an anonymous agent might get narrow pre-claim permissions and a small credit grant.
After a human claims the account, the service might grant post-claim permissions, more credits, a longer lifetime, billing access, or access to more sensitive tools.
Claimed and expired accounts should free capacity for concurrent-unclaimed caps.
The public error should not reveal which limit was hit. A safe response can tell the agent to claim an existing account or retry later.
Privacy and storage
Services should store the normalized origin fields, the hashed connection key, the shared limit key, the suffix, and the generated final account ID.
Services should not store raw IP addresses or raw connection strings in identity records.
Credential values, claim tokens, and one-time codes should continue to be stored as hashes.
Why I think this belongs in auth.md
auth.md already standardizes the anonymous-start and claim flow. Once anonymous-start grants real trial value, services need a common way to meter that value.
This proposal does not require auth.md to prescribe one infrastructure model, one IP model, one provider, or one account ID format. It only gives services a shared vocabulary for origin-aware limits in the anonymous registration path.
Open questions
- Are
channel, source, and user the right names for the optional anonymous request fields?
- Should the service-derived connection concept be named in the spec even though it is not part of the request body?
- Should auth.md recommend fallback values such as
unknown, unknown, and anonymous for anonymous-start registrations?
- Should the spec explicitly recommend separating the shared limit key from the final account/workspace ID?
- Should the spec include guidance that active-unclaimed caps exclude claimed and expired accounts?
We implemented this model in our own SaaS platform work and would be happy to adapt the terminology or shape if there is a better fit for auth.md.
Summary
I would like to propose adding a small origin model to the anonymous-start registration flow in auth.md.
The important scope is narrow: this proposal is only about the
anonymousflow where an agent can receive a usable pre-claim credential before a human has taken ownership. It is not a proposal foridentity_assertionwith ID-JAG,identity_assertionwithverified_email, agent-provider registration, provider-driven revocation, or any other verified identity flow.The reason is simple: SaaS services usually need to give new users some free value before asking them to pay, approve, or fully commit. That can be a trial period, credits, a limited number of tool calls, read-only access, storage, compute, or some other service-specific allowance.
With the anonymous auth.md flow, an agent can start that flow before a human is present. That is useful for product-led onboarding. The agent can try the service, see whether it is useful, and later ask a human to claim the account.
The same thing creates an abuse-control problem. Software can create accounts and consume free resources much faster than a human. If the protocol makes anonymous account creation easy, services need a common way to meter that free access without relying only on blunt IP limits.
Scope
The current README describes three registration flows on
/agent/auth: ID-JAG identity assertion, verified-email identity assertion, and anonymous registration with OTP claim.This issue only concerns the third flow: anonymous registration with OTP claim.
That flow is different because the service immediately creates an agent principal, issues a scoped API key, returns a claim token, and lets the agent operate with pre-claim scopes. The service may later upgrade the same key after a human claim ceremony.
The other two flows already have a stronger identity axis. In the ID-JAG path, a trusted agent provider asserts identity. In the verified-email path, the service sends email before issuing the credential. Those flows may still need rate limits, but I do not think this proposal should change their request shape.
Current anonymous auth.md shape
Today the anonymous request is intentionally small:
{ "type": "anonymous", "requested_credential_type": "api_key" }That is enough for the service to create an anonymous agent account, return a credential, expose pre-claim permissions, and provide the claim information that lets a human take ownership later.
What it does not provide is a standard way to describe where the anonymous registration came from.
That missing origin becomes important once the service grants real trial value to the anonymous account.
What I think is missing
A service may want to limit anonymous registrations by more than one axis.
A simple IP cap is useful, but it is not enough. Many honest users can share one IP address, and abusive traffic can rotate IP addresses.
A simple final-account cap is not enough either. If every anonymous registration creates a new final account or workspace, the cap resets with every new account.
There is also an isolation problem. Two similar agents on the same machine, or two threads of the same app, can produce identical request metadata. If the service derives the final account directly from that metadata, two different credentials can end up pointing at the same unclaimed account.
So I think the anonymous flow needs to distinguish between two things:
Those should not be the same thing.
Proposed anonymous origin values
The existing anonymous request could stay valid, with three optional caller-supplied fields added to the anonymous request shape:
{ "type": "anonymous", "requested_credential_type": "api_key", "channel": "application", "source": "application-a", "user": "123" }channeldescribes what kind of path brought the request. For example:application,agent,browser,system, orunknown.sourceidentifies the app, agent, browser, or system inside that channel.useridentifies the source-local user or subject when that is known. If it is not known, the service can treat it asanonymous.There is also a fourth origin value, which I would call
connection, but it should not be submitted by the agent. The service should derive it from trusted request metadata: the observed IP address, a trusted edge header, a gateway value, a device signal, or another connection signal the service controls.That connection value should be hashed before it is stored.
How the limits would work
The service can normalize the caller-supplied anonymous origin values and combine them with a hashed service-derived connection key:
Example:
This is the shared limit key. It is used for caps and analysis. It is not the final account ID.
The service can then allocate a suffix under that shared key:
The final account ID should still be generated in the service's normal format. For example, a SaaS app may use UUIDv7 or another regular database/account ID format:
If the same anonymous origin registers again, it keeps the same shared limit key, receives the next suffix, and receives another generated account ID:
Both accounts count against the same anonymous origin, but the credentials do not point at the same unclaimed account.
What this enables
With this shape, a service can limit anonymous-start registrations by:
It can also apply different policies before and after claim.
Before claim, an anonymous agent might get narrow pre-claim permissions and a small credit grant.
After a human claims the account, the service might grant post-claim permissions, more credits, a longer lifetime, billing access, or access to more sensitive tools.
Claimed and expired accounts should free capacity for concurrent-unclaimed caps.
The public error should not reveal which limit was hit. A safe response can tell the agent to claim an existing account or retry later.
Privacy and storage
Services should store the normalized origin fields, the hashed connection key, the shared limit key, the suffix, and the generated final account ID.
Services should not store raw IP addresses or raw connection strings in identity records.
Credential values, claim tokens, and one-time codes should continue to be stored as hashes.
Why I think this belongs in auth.md
auth.md already standardizes the anonymous-start and claim flow. Once anonymous-start grants real trial value, services need a common way to meter that value.
This proposal does not require auth.md to prescribe one infrastructure model, one IP model, one provider, or one account ID format. It only gives services a shared vocabulary for origin-aware limits in the anonymous registration path.
Open questions
channel,source, anduserthe right names for the optional anonymous request fields?unknown,unknown, andanonymousfor anonymous-start registrations?We implemented this model in our own SaaS platform work and would be happy to adapt the terminology or shape if there is a better fit for auth.md.