# Submo auth.md — how an agent gets credentials for submoapp.com

Canonical: https://submoapp.com/auth.md · Last updated: 2026-08-24

This walkthrough is for agents. Humans use [Developers](https://submoapp.com/developers) or the dashboard. The authoritative endpoint URLs live in Protected Resource Metadata (PRM). This file is the prose companion (`agent_auth`, `register_uri`, `identity_assertion`, `id-jag`, `WWW-Authenticate`).

Resource server: `https://api.submoapp.com`
Authorization server: `https://api.submoapp.com`

## 1. Discover

On a 401, read `WWW-Authenticate: Bearer resource_metadata="…"`. Fall back to:

`GET https://api.submoapp.com/.well-known/oauth-protected-resource`

Read `resource`, `resource_name`, `authorization_servers`, `scopes_supported`, and `bearer_methods_supported`.

Then fetch Authorization Server metadata:

`GET https://api.submoapp.com/.well-known/oauth-authorization-server`

Read `issuer`, `authorization_endpoint`, `token_endpoint`, `revocation_endpoint`, `registration_endpoint`, `grant_types_supported`, `code_challenge_methods_supported` (must include `S256`), and the `agent_auth` block in full:

- `skill`
- `identity_endpoint` (`register_uri`)
- `claim_endpoint` (`claim_uri`)
- `events_endpoint`
- `identity_types_supported`
- `identity_assertion.assertion_types_supported`
- `events_supported`

## 2. Pick a method

- Session you can exchange for an ID-JAG bound to this audience → `identity_assertion` (not enabled; `identity_assertion.assertion_types_supported` is empty).
- Only the user's email → `service_auth` (claim ceremony required).
- Existing Submo member in a browser → OAuth `authorization_code` + PKCE S256.
- Neither → do not use `anonymous`. It is not_enabled.

## 3. Register

### service_auth

```http
POST https://api.submoapp.com/agent/identity
Content-Type: application/json

{"type":"service_auth","login_hint":"alex@example.com","plan_id":"weekly"}
```

Success returns `claim_token` and a `claim` block (`user_code`, `verification_uri`). The user opens Stripe Checkout at `verification_uri`. No credential is returned yet.

### authorization_code

1. Optional: `POST https://api.submoapp.com/oauth/register` with `redirect_uris`.
2. Send the user to `GET https://api.submoapp.com/oauth/authorize?client_id=…&redirect_uri=…&response_type=code&code_challenge=…&code_challenge_method=S256&state=…`.
3. They sign in on Submo and approve scopes.

## 4. Claim

4a. `service_auth` already includes the `claim` block.
4b. Hand `verification_uri` and `user_code` to the user. They pay on a page Submo owns (Stripe Checkout).
4c. Poll:

```http
POST https://api.submoapp.com/oauth/token
Content-Type: application/json

{"grant_type":"urn:workos:agent-auth:grant-type:claim","claim_token":"clm_…"}
```

`authorization_pending` means keep polling. On success you receive `access_token`.

## 5. Exchange the assertion

identity_assertion / id-jag JWT-bearer is not enabled. For OAuth codes:

`grant_type=authorization_code&code=…&code_verifier=…&redirect_uri=…`

There is no refresh_token. Re-run registration or mint a dashboard key when the access_token is revoked.

## 6. Use the credential

`Authorization: Bearer submo_live_…`

Public research (`GET /v1/plans`, cancel guides, price indexes) needs no credential.

## 7. Errors

| Code | Endpoint | Action |
| --- | --- | --- |
| unauthorized | any paid route | Complete checkout or OAuth |
| authorization_pending | /oauth/token | Wait and retry claim |
| validation_error | /agent/identity | Fix login_hint / plan_id |
| *_not_enabled | /agent/identity | Use service_auth or authorization_code |
| expired_token | /oauth/token | Re-call /agent/identity |

## 8. Revocation

`POST https://api.submoapp.com/oauth/revoke` with the access_token, or delete the key at [Account](https://submoapp.com/dashboard/account).
