> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aveid.net/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth + OIDC endpoints

> Detailed endpoint contract for app metadata, authorize, token grants, userinfo, discovery, and delegation management.

<Tip>
  The token endpoint accepts both OAuth-standard `snake_case` fields and legacy `camelCase` fields. Prefer `grant_type`, `client_id`, `redirect_uri`, and `code_verifier` in new integrations.
</Tip>

## `GET /api/oauth/app/:clientId`

Returns app public metadata and active connector resources.

Errors:

* `404` app not found

## `GET /api/oauth/resource/:resourceKey`

Returns active connector resource metadata plus owner app info.

Errors:

* `404` resource not found

## `POST /api/oauth/authorize` (session-authenticated)

Used by authenticated Ave session context to grant app authorization and produce redirect URL with code.

Body fields include:

<ParamField path="clientId" type="string" required>
  OAuth client identifier.
</ParamField>

<ParamField path="redirectUri" type="string" required>
  Must exactly match one of the app's registered redirect URIs.
</ParamField>

<ParamField path="identityId" type="uuid" required>
  Identity selected by the authenticated user.
</ParamField>

<ParamField path="scope" type="string">
  Space-separated scopes; validated against app allowlist.
</ParamField>

<ParamField path="codeChallenge" type="string">
  PKCE challenge for public-client authorization.
</ParamField>

<ParamField path="encryptedAppKey" type="string">
  Required for first E2EE authorization when the app supports E2EE.
</ParamField>

<ParamField path="requestedResource" type="string">
  Connector resource key when `connector=true`.
</ParamField>

<ParamField path="requestedScope" type="string">
  Connector scope set requested by source app.
</ParamField>

Notable behavior:

* Scope validation against app allowlist
* E2EE key required for E2EE app first authorization
* Connector grant create/update with merged scopes

## `POST /api/oauth/token`

Supported `grant_type` values:

1. `authorization_code`
2. `refresh_token`
3. `urn:ietf:params:oauth:grant-type:token-exchange`

<Tabs>
  <Tab title="authorization_code">
    ```json theme={null}
    {
    	"grant_type": "authorization_code",
    	"code": "AUTHORIZATION_CODE",
    	"redirect_uri": "https://yourapp.com/callback",
    	"client_id": "YOUR_CLIENT_ID",
    	"code_verifier": "PKCE_VERIFIER"
    }
    ```
  </Tab>

  <Tab title="refresh_token">
    ```json theme={null}
    {
    	"grant_type": "refresh_token",
    	"refresh_token": "rt_...",
    	"client_id": "YOUR_CLIENT_ID",
    	"client_secret": "YOUR_CLIENT_SECRET"
    }
    ```
  </Tab>

  <Tab title="token_exchange">
    ```json theme={null}
    {
    	"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
    	"subject_token": "SOURCE_APP_ACCESS_TOKEN",
    	"resource": "target:resource",
    	"scope": "resource.access",
    	"client_id": "YOUR_CLIENT_ID",
    	"client_secret": "YOUR_CLIENT_SECRET"
    }
    ```
  </Tab>
</Tabs>

### Authorization code grant

Validates code, expiry, redirect URI, and either:

* PKCE verifier (if code challenge present), or
* client secret

Returns tokens and a scope-filtered `user` payload: `id` is always present, display name/handle/avatar require `profile`, and email requires `email`.

<ResponseField name="access_token" type="string">
  Opaque bearer token accepted by Ave APIs.
</ResponseField>

<ResponseField name="access_token_jwt" type="string">
  Signed JWT for Ave API authorization. Its audience is Ave's resource audience.
</ResponseField>

<ResponseField name="id_token" type="string">
  Returned when `openid` scope is granted.
</ResponseField>

<ResponseField name="refresh_token" type="string">
  Returned when `offline_access` scope is granted.
</ResponseField>

### Refresh token grant

Validates the token hash record, client ownership, active app authorization, and revoked/reuse/expiry state, then rotates the refresh token and returns new access and refresh tokens with the granted `scope`. An `id_token` is included only with `openid`. Reuse detection is scoped to the rotated token lineage that was reused, not unrelated refresh tokens held by the same app on other devices. Disconnecting an app removes its authorization codes, Ave access tokens, refresh tokens, and connector grants for that identity; reconnecting does not restore old tokens.

### Token exchange grant (Connector)

Exchanges source app token for delegated target token after grant and scope checks.

## FedCM endpoints

Ave also exposes an additive FedCM flow for supported browsers.

### `GET /api/oauth/fedcm/config`

Returns the Ave FedCM provider configuration.

### `GET /api/oauth/fedcm/accounts`

Returns the currently signed-in Ave identities for the browser session.

### `POST /api/oauth/fedcm/assertion`

Returns either:

* a short-lived Ave FedCM assertion token, or
* a `continue_on` URL when consent or E2EE setup must finish in a dialog

Direct completion requires an existing authorization covering every requested scope. New scopes, including `user_id`, require consent. Older authorizations without recorded consent scopes also require consent. Standard scopes must be in the app’s current allowlist at assertion and exchange time.

### `POST /api/oauth/fedcm/finalize`

Used by Ave's continuation UI to turn a completed consent step into a short-lived FedCM assertion.

### `POST /api/oauth/fedcm/exchange`

Exchanges a FedCM assertion for the normal token payload. For E2EE apps, the signed FedCM assertion can also carry `app_key` after Ave's continuation dialog completes the existing key-unlock flow.

## `GET /api/oauth/userinfo`

Authorization: `Bearer <access_token_or_access_token_jwt>`

Returns claims based on granted scopes:

* always `sub`
* `profile`: `name`, `preferred_username`, `picture`
* `email`: `email`
* `user_id`: when included in the authorized scope
* `organization`: present when the token was issued with Ave Business organization context

Organization context shape:

```json theme={null}
{
  "organization": {
    "id": "org_...",
    "name": "Example Co",
    "memberId": "orgmem_...",
    "role": "admin",
    "scopes": ["read", "sign", "approve"],
    "signingAuthority": true,
    "encryptionMode": "standard",
    "keyCustody": "ave_standard",
    "authMethod": "enterprise_sso",
    "ssoConnectionId": "sso_...",
    "e2eeKeyDelivery": "ave_identity_grants_only"
  }
}
```

<AccordionGroup>
  <Accordion title="Why userinfo may omit fields">
    Claims are scope-gated. Missing `email` or profile fields usually means those scopes were not granted.
  </Accordion>

  <Accordion title="Accepted token formats">
    The endpoint accepts both opaque `access_token` and JWT `access_token_jwt` when valid and unexpired.
  </Accordion>
</AccordionGroup>

Errors:

* `401 unauthorized` missing bearer token
* `401 invalid_token` invalid/expired/unresolvable token

## `GET /api/oauth/organizations`

Authorization: `Bearer <access_token_or_access_token_jwt>`

Lists the active Ave Business organizations for the token identity. Apps use this after a normal sign-in to build a workspace picker, then pass the selected organization `id` as `organizationId` in the next authorization request.

Optional query:

<ParamField path="client_id" type="string">
  When present, Ave verifies that the bearer token belongs to that client.
</ParamField>

Response:

```json theme={null}
{
  "organizations": [
    {
      "id": "org_...",
      "name": "Example Co",
      "slug": "example-co",
      "logoUrl": "https://...",
      "role": "admin",
      "scopes": ["read", "sign", "approve"],
      "signingAuthority": true,
      "ssoRequired": true,
      "encryptionMode": "standard",
      "keyCustody": "ave_standard"
    }
  ]
}
```

Errors:

* `401 unauthorized` missing bearer token
* `401 invalid_token` invalid/expired/unresolvable token
* `403 invalid_client` token does not belong to the requested `client_id`

## `POST /api/oauth/workspaces`

Authorization: `Bearer <access_token>`

Creates a new Ave Business workspace for the token identity. Apps should call this only after the user confirms that the app will create an Ave-managed workspace.

Body:

```json theme={null}
{
  "name": "Example Co",
  "client_id": "YOUR_CLIENT_ID",
  "userConfirmedAveWorkspaceCreation": true
}
```

<ParamField path="name" type="string" required>
  Display name for the new Ave workspace.
</ParamField>

<ParamField path="client_id" type="string">
  Verifies that the bearer token belongs to your app.
</ParamField>

<ParamField path="userConfirmedAveWorkspaceCreation" type="boolean" required>
  Must be `true`. Set it only after your UI tells the user that this creates an Ave Business workspace.
</ParamField>

Response:

```json theme={null}
{
  "organization": {
    "id": "org_...",
    "name": "Example Co",
    "slug": "example-co",
    "logoUrl": null,
    "role": "owner",
    "scopes": ["read", "sign", "approve", "manage_identities", "manage_keys", "manage_sso", "manage_org"],
    "signingAuthority": true,
    "ssoRequired": false,
    "encryptionMode": "standard",
    "keyCustody": "ave_standard"
  }
}
```

The returned `organization.id` is the `organizationId` to use in the next authorization request.

Errors:

* `400 invalid_request` mismatched `clientId` and `client_id`
* `400 workspace_creation_failed` the identity could not create the workspace
* `401 unauthorized` missing bearer token
* `401 invalid_token` invalid/expired/unresolvable token
* `403 invalid_client` token is not for a registered Ave app or does not match `client_id`

## `GET /.well-known/openid-configuration`

Returns issuer metadata, endpoints, supported scopes, response types, grant types, and signing algs.

## `GET /.well-known/jwks.json`

Returns signing key set for JWT verification.

## `GET /.well-known/webfinger?resource=...`

Returns issuer discovery link for requested resource.

## `GET /api/oauth/delegations` (session-authenticated)

Lists user connector grants with source app and target resource context.

## `DELETE /api/oauth/delegations/:delegationId` (session-authenticated)

Revokes active delegation grant and records audit log.

<Check>
  After revocation, token-exchange attempts for that grant fail until the user re-authorizes connector access.
</Check>
