Let an agent call your APIs on a user's behalf
This page explains the on-behalf-of (OBO) flow for a custom API and where it differs from the current Qoni SDK product surface.
Current SDK boundary
The named APIs in @qoniai/qoni@0.4.1 cover GUMem and Web Agent and accept only product scopes known to the SDK. The package does not expose a named end-to-end method that accepts an arbitrary custom resource and arbitrary custom scopes.
Do not use QoniScopes.WEB_SEARCH_*, DO_ANYTHING_*, or another Qoni product permission in a “your API” example. Do not present qoni.webSearch.run() as a custom-API call. Configure the custom resource and scopes in GenAuth, then use the HTTP API reference for that protocol flow.
Two different call paths
| Target resource | Recommended call path | Token handling |
|---|---|---|
| Qoni GUMem / Web Agent | Use qoni.gumem.*, qoni.webSearch.*, qoni.doAnything.*, qoni.deepResearch.*, or qoni.track.* | Pass grant.token to the named method; the SDK performs product token exchange internally |
| Your custom API | Use the GenAuth HTTP contract and the resource / scopes you configured | Exchange for an access token whose audience is your API, then validate it at your resource server |
The paths share delegation, audit, and attenuation concepts, but their code and scopes are not interchangeable.
Custom-API OBO flow
1. Configure the resource and scopes
Register your API resource identifier and delegable scopes in GenAuth. The resource, scope names, and token audience must come from real configuration, not from Qoni Web Agent constants.
2. Start user authorization
Your trusted backend creates an interactive delegation through the GenAuth HTTP API. The request includes:
- A stable Agent identifier.
- Scopes registered for your API.
- A registered
redirectUri. - An anti-replay
stategenerated and stored by your server.
Interactive mode resolves the signed-in user on the authorization page. Do not send the removed task, agentKey, or top-level userId SDK model.
3. Complete the callback
After approval, the callback brings back a one-time code, your business state, and grant_state, but no grantId. Look up the record you saved by grant_state and check the business state, then complete the exchange with the saved grantId, the callback code, and grant_state passed as state. Keep the delegation token on a trusted server.
4. Exchange for a custom-API access token
Follow the token-exchange contract in the API reference. resource must be your registered API resource, and scopes must be a subset of the scopes actually granted.
Do not use webagent as a placeholder custom resource. That targets Qoni Web Agent, not your service.
5. Validate at the resource server
When your API receives Authorization: Bearer <access-token>, it must at least:
- Verify the signature with trusted JWKS.
- Validate
iss,aud,exp, andnbf. - Identify the represented user from
suband apply business authorization. - Identify the acting Agent from
actand record it in audit logs. - Verify that scopes cover the current operation.
- Reject requests whose audience, user, Agent, or scopes do not match.
See Protect your APIs for the resource-side checklist and Token and claim reference for claim shapes.
Acceptance checks
- A read-only token must be rejected by a write endpoint.
- The access-token
audmust identify your API, notwebagentor another Qoni product. sub,act, scopes, grant ID, and audit ID must join into one audit chain.- After revocation, later exchanges or calls fail according to GenAuth revocation policy.
If you only need a Qoni product
Do not use the custom-API path above. After delegateToken(), pass grant.token directly to the named product method:
const search = await qoni.webSearch.run({
token: grant.token,
prompt: 'Qoni Agent Identity',
})
const result = await search.wait()