A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: update third party api client info

Trezy e6a9e157 7d4c44a7

+148 -49
+1 -1
packages/docs/docs/getting-started/authentication.md
··· 318 318 - [Permissions](../guides/admin/permissions.md) — full list of permissions and what each one grants 319 319 - [API Keys](../guides/admin/api-keys.md) — create scoped admin API keys for automation 320 320 - [Admin API — API Clients](../reference/admin/api-clients.md) — register API clients and configure rate limits 321 - - [Self-Service API Clients](../reference/oauth/api-clients.md) — let third-party apps create child API clients programmatically 321 + - [Third-Party API Clients](../reference/oauth/api-clients.md) — let third-party apps manage their own API clients programmatically
+2 -2
packages/docs/docs/reference/admin/api-clients.md
··· 6 6 7 7 Each client has an `hvc_`-prefixed client key and an `hvs_`-prefixed client secret. The secret is only returned at creation and is sha256-hashed in the database. Server-to-server callers pass the secret as `X-Client-Secret`. Browser callers use the `Origin` header, which is matched against the client's `client_uri`. Mismatches currently log warnings rather than rejecting the request, but rate limiting applies either way. See [Authentication — XRPC](../../getting-started/authentication.md#xrpc-api-client-identification) for the client-side view, and the [API Keys guide](../../guides/admin/api-keys.md) for how admin API keys differ from API clients. 8 8 9 - :::tip Self-service API clients 10 - Third-party apps can also create **child API clients** programmatically via the [self-service endpoint](../oauth/api-clients.md), without needing admin access. 9 + :::tip Third-Party API Clients 10 + Third-party apps can also create, list, and delete their own API clients programmatically via the [XRPC API](../oauth/api-clients.md), without needing admin access. 11 11 ::: 12 12 13 13 ```sh
+144 -45
packages/docs/docs/reference/oauth/api-clients.md
··· 1 - # OAuth API: Self-Service API Clients 1 + # Third-Party API Clients 2 2 3 - Third-party applications can create child API clients on behalf of authenticated users via `POST /oauth/api-clients`. A child client is always tied to exactly one parent — the admin-created top-level API client that made the request. Only one level of nesting is allowed; child clients cannot create further children. Each child client gets its own rate limit bucket with instance default settings. 3 + Third-party applications can manage their own API clients via the `dev.happyview.*` XRPC endpoints. A third-party client is always tied to exactly one parent — the admin-created top-level API client whose DPoP session made the request. Only one level of nesting is allowed; third-party clients cannot create further children. Each third-party client gets its own rate limit bucket with instance default settings. 4 4 5 - The endpoint uses [DPoP authentication](../../getting-started/authentication.md#authenticating-users-for-procedures). See the [admin API client docs](../admin/api-clients.md) for managing clients through the admin API, and the [API Clients guide](../../guides/features/api-clients.md) for an overview of how API clients work in HappyView. 5 + All endpoints use [DPoP authentication](../../getting-started/authentication.md#authenticating-users-for-procedures). See the [admin API client docs](../admin/api-clients.md) for managing clients through the admin API, and the [API Clients guide](../../guides/features/api-clients.md) for how API clients work. 6 6 7 - ## Create a child client 7 + :::note 8 + Only top-level API clients can call these endpoints. Third-party (child) clients receive `401 Unauthorized` or `403 Forbidden`. 9 + ::: 8 10 9 - ``` 10 - POST /oauth/api-clients 11 - ``` 11 + ## Authentication 12 12 13 - Requires three headers: 13 + All requests require three headers: 14 14 15 15 | Header | Value | 16 16 | --------------- | ------------------------------------------------------------ | 17 17 | `Authorization` | `DPoP <access_token>` | 18 - | `DPoP` | A DPoP proof JWT (method: `POST`, htu: the full request URL) | 18 + | `DPoP` | A DPoP proof JWT (method matches the HTTP method, `htu` is scheme + host + path, no query string) | 19 19 | `X-Client-Key` | The parent client's `client_key` | 20 20 21 - The access token must belong to a valid DPoP session for the parent client. The parent client's owner (its `created_by` DID) must exist in the HappyView `users` table. 21 + The access token must belong to a valid DPoP session for the parent client. 22 + 23 + ## List clients 24 + 25 + ``` 26 + GET /xrpc/dev.happyview.listApiClients 27 + ``` 28 + 29 + Returns all API clients owned by the authenticated user. 30 + 31 + **Response**: `200 OK` 32 + 33 + ```json 34 + { 35 + "clients": [ 36 + { 37 + "id": "550e8400-e29b-41d4-a716-446655440000", 38 + "clientKey": "hvc_a1b2c3d4e5f6...", 39 + "name": "My App", 40 + "clientIdUrl": "https://myapp.example.com/client-metadata.json", 41 + "clientUri": "https://myapp.example.com", 42 + "redirectUris": ["https://myapp.example.com/callback"], 43 + "clientType": "confidential", 44 + "scopes": "atproto", 45 + "allowedOrigins": [], 46 + "isActive": true, 47 + "createdAt": "2026-04-28T12:00:00Z" 48 + } 49 + ] 50 + } 51 + ``` 52 + 53 + ## Get a client 54 + 55 + ``` 56 + GET /xrpc/dev.happyview.getApiClient?id=<client_id> 57 + ``` 58 + 59 + | Parameter | Type | Required | Description | 60 + | --------- | ------ | -------- | ----------------- | 61 + | `id` | string | yes | The client's UUID | 62 + 63 + **Response**: `200 OK` 64 + 65 + ```json 66 + { 67 + "client": { 68 + "id": "550e8400-e29b-41d4-a716-446655440000", 69 + "clientKey": "hvc_a1b2c3d4e5f6...", 70 + "name": "My App", 71 + "clientIdUrl": "https://myapp.example.com/client-metadata.json", 72 + "clientUri": "https://myapp.example.com", 73 + "redirectUris": ["https://myapp.example.com/callback"], 74 + "clientType": "confidential", 75 + "scopes": "atproto", 76 + "allowedOrigins": [], 77 + "isActive": true, 78 + "createdAt": "2026-04-28T12:00:00Z" 79 + } 80 + } 81 + ``` 82 + 83 + Returns `404` if the client doesn't exist or isn't owned by the authenticated user. 84 + 85 + ## Create a client 86 + 87 + ``` 88 + POST /xrpc/dev.happyview.createApiClient 89 + ``` 22 90 23 91 ```sh 24 - curl -X POST https://happyview.example.com/oauth/api-clients \ 92 + curl -X POST https://happyview.example.com/xrpc/dev.happyview.createApiClient \ 25 93 -H "X-Client-Key: hvc_parent_key" \ 26 94 -H "Authorization: DPoP eyJhbG..." \ 27 95 -H "DPoP: eyJhbG..." \ 28 96 -H "Content-Type: application/json" \ 29 97 -d '{ 30 - "name": "My Child App", 31 - "client_id_url": "https://child.example.com/client-metadata.json", 32 - "client_uri": "https://child.example.com", 33 - "redirect_uris": ["https://child.example.com/callback"], 34 - "client_type": "confidential" 98 + "name": "My Third-Party App", 99 + "clientIdUrl": "https://myapp.example.com/client-metadata.json", 100 + "clientUri": "https://myapp.example.com", 101 + "redirectUris": ["https://myapp.example.com/callback"], 102 + "clientType": "confidential" 35 103 }' 36 104 ``` 37 105 38 - | Field | Type | Required | Description | 39 - | ----------------- | -------- | -------- | ------------------------------------------------ | 40 - | `name` | string | yes | Display name for the child client | 41 - | `client_id_url` | string | yes | Unique OAuth client ID URL | 42 - | `client_uri` | string | yes | The client's homepage URL | 43 - | `redirect_uris` | string[] | yes | OAuth redirect URIs | 44 - | `scopes` | string | no | Space-separated OAuth scopes (default `"atproto"`) | 45 - | `client_type` | string | no | `"confidential"` or `"public"` (default `"confidential"`) | 46 - | `allowed_origins` | string[] | no | CORS allowed origins | 106 + | Field | Type | Required | Description | 107 + | ----------------- | -------- | -------- | -------------------------------------------------------------- | 108 + | `name` | string | yes | Display name for the client | 109 + | `clientIdUrl` | string | yes | Unique OAuth client ID URL | 110 + | `clientUri` | string | yes | The client's homepage URL | 111 + | `redirectUris` | string[] | yes | OAuth redirect URIs | 112 + | `scopes` | string | no | Space-separated OAuth scopes (default `"atproto"`) | 113 + | `clientType` | string | no | `"confidential"` or `"public"` (default `"confidential"`) | 114 + | `allowedOrigins` | string[] | no | CORS allowed origins (relevant for public clients) | 47 115 48 116 **Response**: `201 Created` 49 117 50 118 ```json 51 119 { 52 - "id": "550e8400-e29b-41d4-a716-446655440000", 53 - "client_key": "hvc_a1b2c3d4e5f6...", 54 - "client_secret": "hvs_f6e5d4c3b2a1...", 55 - "name": "My Child App", 56 - "client_id_url": "https://child.example.com/client-metadata.json", 57 - "client_type": "confidential" 120 + "client": { 121 + "id": "550e8400-e29b-41d4-a716-446655440000", 122 + "clientKey": "hvc_a1b2c3d4e5f6...", 123 + "name": "My Third-Party App", 124 + "clientIdUrl": "https://myapp.example.com/client-metadata.json", 125 + "clientUri": "https://myapp.example.com", 126 + "redirectUris": ["https://myapp.example.com/callback"], 127 + "clientType": "confidential", 128 + "scopes": "atproto", 129 + "allowedOrigins": [], 130 + "isActive": true, 131 + "createdAt": "2026-04-28T12:00:00Z" 132 + }, 133 + "clientSecret": "hvs_f6e5d4c3b2a1..." 58 134 } 59 135 ``` 60 136 61 - The `client_secret` is only present for confidential clients and is only returned in this response — store it securely. It is stored as a SHA-256 hash and cannot be retrieved again. 137 + The `clientSecret` is only present for confidential clients and is only returned in this response. It is stored as a SHA-256 hash and cannot be retrieved again. 138 + 139 + ## Delete a client 140 + 141 + ``` 142 + POST /xrpc/dev.happyview.deleteApiClient 143 + ``` 144 + 145 + ```sh 146 + curl -X POST https://happyview.example.com/xrpc/dev.happyview.deleteApiClient \ 147 + -H "X-Client-Key: hvc_parent_key" \ 148 + -H "Authorization: DPoP eyJhbG..." \ 149 + -H "DPoP: eyJhbG..." \ 150 + -H "Content-Type: application/json" \ 151 + -d '{ "id": "550e8400-e29b-41d4-a716-446655440000" }' 152 + ``` 153 + 154 + | Field | Type | Required | Description | 155 + | ----- | ------ | -------- | ----------------- | 156 + | `id` | string | yes | The client's UUID | 157 + 158 + **Response**: `200 OK` with `{}` 159 + 160 + Returns `404` if the client doesn't exist or isn't owned by the authenticated user. Deleting a client cascades to all its children. 62 161 63 162 ## Errors 64 163 65 - | Status | Error | Cause | 66 - | ------ | ---------------------------------------- | ------------------------------------------------------------------ | 67 - | 400 | `Invalid client_type` | `client_type` is not `"confidential"` or `"public"` | 68 - | 400 | `invalid request body` | Missing required fields or malformed JSON | 69 - | 401 | `Missing client identification` | `X-Client-Key` header is absent | 70 - | 401 | `DPoP authorization scheme required` | `Authorization` header doesn't start with `DPoP ` | 71 - | 401 | `DPoP proof header required` | `DPoP` header is absent | 72 - | 401 | `token_expired` | The access token has expired | 73 - | 401 | `Invalid client` | `X-Client-Key` doesn't match a known client | 74 - | 403 | `Child clients cannot create API clients` | The calling client is itself a child | 75 - | 403 | `Parent client owner not found` | The parent client's `created_by` DID is not in the `users` table | 76 - | 409 | `client_id_url already registered` | Another client already uses that `client_id_url` | 164 + | Status | Error | Cause | 165 + | ------ | ----------------------------------------- | ---------------------------------------------------------------- | 166 + | 400 | `Invalid client_type` | `client_type` is not `"confidential"` or `"public"` | 167 + | 400 | `invalid request body` | Missing required fields or malformed JSON | 168 + | 401 | `requires DPoP authentication` | `Authorization` header is missing or doesn't use the DPoP scheme | 169 + | 401 | `requires an API client key` | `X-Client-Key` header is absent | 170 + | 401 | `token_expired` | The access token has expired | 171 + | 401 | `Invalid client` | `X-Client-Key` doesn't match a known client | 172 + | 401 | `child clients cannot manage API clients` | The calling client is itself a third-party (child) client | 173 + | 403 | `Child clients cannot create API clients` | The calling client is itself a third-party (child) client | 174 + | 404 | `API client not found` | No client with that ID owned by the authenticated user | 175 + | 409 | `client_id_url already registered` | Another client already uses that `clientIdUrl` | 77 176 78 177 ## Operational notes 79 178 80 - Each child client gets its own rate limit bucket using the instance's default capacity and refill rate (`DEFAULT_RATE_LIMIT_CAPACITY` / `DEFAULT_RATE_LIMIT_REFILL_RATE`). Deactivating or deleting a parent via the [admin API](../admin/api-clients.md) cascades to all its children. 179 + Each third-party client gets its own rate limit bucket using the instance's default capacity and refill rate (`DEFAULT_RATE_LIMIT_CAPACITY` / `DEFAULT_RATE_LIMIT_REFILL_RATE`). Deactivating or deleting a parent via the [admin API](../admin/api-clients.md) cascades to all its children. 81 180 82 181 The admin API clients list (`GET /admin/api-clients`) returns `parent_client_id` and `owner_did` fields for each client and supports `?parent_id=` filtering. The dashboard's API Clients table shows these as "Parent Client" and "Owner" columns.
+1 -1
packages/docs/sidebars.ts
··· 386 386 { 387 387 type: "doc", 388 388 id: "reference/oauth/api-clients", 389 - label: "Self-Service API Clients", 389 + label: "Third-Party API Clients", 390 390 }, 391 391 ], 392 392 },