···11+ALTER TABLE api_clients ADD COLUMN parent_client_id TEXT REFERENCES api_clients(id) ON DELETE CASCADE;
22+ALTER TABLE api_clients ADD COLUMN owner_did TEXT;
33+44+CREATE INDEX idx_api_clients_parent_id ON api_clients(parent_client_id);
55+CREATE INDEX idx_api_clients_owner_did ON api_clients(owner_did);
···11+ALTER TABLE api_clients ADD COLUMN parent_client_id TEXT REFERENCES api_clients(id) ON DELETE CASCADE;
22+ALTER TABLE api_clients ADD COLUMN owner_did TEXT;
33+44+CREATE INDEX idx_api_clients_parent_id ON api_clients(parent_client_id);
55+CREATE INDEX idx_api_clients_owner_did ON api_clients(owner_did);
···318318- [Permissions](../guides/admin/permissions.md) — full list of permissions and what each one grants
319319- [API Keys](../guides/admin/api-keys.md) — create scoped admin API keys for automation
320320- [Admin API — API Clients](../reference/admin/api-clients.md) — register API clients and configure rate limits
321321+- [Self-Service API Clients](../reference/oauth/api-clients.md) — let third-party apps create child API clients programmatically
···2233This guide covers how to build your own HappyView WASM plugins. For installing and configuring plugins, see the [Plugins guide](plugins.md).
4455-See the [happyview-plugins](https://github.com/gamesgamesgamesgamesgames/happyview-plugins) repository for examples and the plugin SDK.
55+See the [happyview-plugins](https://tangled.org/gamesgamesgamesgames.games/happyview-plugins) repository for examples and the plugin SDK.
6677## Plugin Manifest
88···9999100100## Next steps
101101102102-- [Official plugins repository](https://github.com/gamesgamesgamesgamesgames/happyview-plugins) — ready-to-use plugins and the plugin SDK
102102+- [Official plugins repository](https://tangled.org/gamesgamesgamesgames.games/happyview-plugins) — ready-to-use plugins and the plugin SDK
103103- [Plugins guide](plugins.md) — install and configure plugins
104104- [API Keys](../admin/api-keys.md) — authenticate programmatic access to admin endpoints
105105- [Permissions](../admin/permissions.md) — configure user access to plugin management
+2-2
packages/docs/docs/guides/features/plugins.md
···2233HappyView uses WASM plugins to extend its functionality. Plugins can integrate with external platforms, sync data to users' atproto identities, and more. Auth plugins — the first supported plugin type — enable users to link accounts from platforms like Steam, Xbox, itch.io, and others, then sync data like game libraries.
4455-Official plugins for Steam, Xbox, itch.io, and other platforms are available in the [happyview-plugins](https://github.com/gamesgamesgamesgamesgames/happyview-plugins) repository.
55+Official plugins for Steam, Xbox, itch.io, and other platforms are available in the [happyview-plugins](https://tangled.org/gamesgamesgamesgames.games/happyview-plugins) repository.
6677## Installing Plugins
88···7474## Next steps
75757676- [Developing Plugins](developing-plugins.md) — create your own plugins with the WASM plugin API
7777-- [Official plugins repository](https://github.com/gamesgamesgamesgamesgames/happyview-plugins) — ready-to-use plugins for Steam, Xbox, itch.io, and more
7777+- [Official plugins repository](https://tangled.org/gamesgamesgamesgames.games/happyview-plugins) — ready-to-use plugins for Steam, Xbox, itch.io, and more
7878- [API Keys](../admin/api-keys.md) — authenticate programmatic access to admin endpoints
7979- [Permissions](../admin/permissions.md) — configure user access to plugin management
+1-1
packages/docs/docs/guides/upgrading-to-v2.md
···11# Migrating from v1
2233-v2 consolidates HappyView, [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap), and [AIP](https://github.com/graze-social/aip) into a single binary. Real-time indexing, backfill, and OAuth are now built in — there are no companion services to deploy.
33+v2 consolidates HappyView, [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap), and [AIP](https://tangled.org/gamesgamesgamesgames.games/aip) into a single binary. Real-time indexing, backfill, and OAuth are now built in — there are no companion services to deploy.
4455This guide covers every breaking change and the steps to migrate.
66
+7-1
packages/docs/docs/reference/admin/api-clients.md
···6677Each 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.
8899+:::tip Self-service API clients
1010+Third-party apps can also create **child API clients** programmatically via the [self-service endpoint](../oauth/api-clients.md), without needing admin access.
1111+:::
1212+913```sh
1014# All examples assume $TOKEN is an API key (hv_...)
1115AUTH="Authorization: Bearer $TOKEN"
···4044 "is_active": true,
4145 "created_by": "did:plc:...",
4246 "created_at": "2026-04-13T12:00:00Z",
4343- "updated_at": "2026-04-13T12:00:00Z"
4747+ "updated_at": "2026-04-13T12:00:00Z",
4848+ "parent_client_id": null,
4949+ "owner_did": null
4450 }
4551]
4652```
+82
packages/docs/docs/reference/oauth/api-clients.md
···11+# OAuth API: Self-Service API Clients
22+33+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.
44+55+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.
66+77+## Create a child client
88+99+```
1010+POST /oauth/api-clients
1111+```
1212+1313+Requires three headers:
1414+1515+| Header | Value |
1616+| --------------- | ------------------------------------------------------------ |
1717+| `Authorization` | `DPoP <access_token>` |
1818+| `DPoP` | A DPoP proof JWT (method: `POST`, htu: the full request URL) |
1919+| `X-Client-Key` | The parent client's `client_key` |
2020+2121+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.
2222+2323+```sh
2424+curl -X POST https://happyview.example.com/oauth/api-clients \
2525+ -H "X-Client-Key: hvc_parent_key" \
2626+ -H "Authorization: DPoP eyJhbG..." \
2727+ -H "DPoP: eyJhbG..." \
2828+ -H "Content-Type: application/json" \
2929+ -d '{
3030+ "name": "My Child App",
3131+ "client_id_url": "https://child.example.com/client-metadata.json",
3232+ "client_uri": "https://child.example.com",
3333+ "redirect_uris": ["https://child.example.com/callback"],
3434+ "client_type": "confidential"
3535+ }'
3636+```
3737+3838+| Field | Type | Required | Description |
3939+| ----------------- | -------- | -------- | ------------------------------------------------ |
4040+| `name` | string | yes | Display name for the child client |
4141+| `client_id_url` | string | yes | Unique OAuth client ID URL |
4242+| `client_uri` | string | yes | The client's homepage URL |
4343+| `redirect_uris` | string[] | yes | OAuth redirect URIs |
4444+| `scopes` | string | no | Space-separated OAuth scopes (default `"atproto"`) |
4545+| `client_type` | string | no | `"confidential"` or `"public"` (default `"confidential"`) |
4646+| `allowed_origins` | string[] | no | CORS allowed origins |
4747+4848+**Response**: `201 Created`
4949+5050+```json
5151+{
5252+ "id": "550e8400-e29b-41d4-a716-446655440000",
5353+ "client_key": "hvc_a1b2c3d4e5f6...",
5454+ "client_secret": "hvs_f6e5d4c3b2a1...",
5555+ "name": "My Child App",
5656+ "client_id_url": "https://child.example.com/client-metadata.json",
5757+ "client_type": "confidential"
5858+}
5959+```
6060+6161+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.
6262+6363+## Errors
6464+6565+| Status | Error | Cause |
6666+| ------ | ---------------------------------------- | ------------------------------------------------------------------ |
6767+| 400 | `Invalid client_type` | `client_type` is not `"confidential"` or `"public"` |
6868+| 400 | `invalid request body` | Missing required fields or malformed JSON |
6969+| 401 | `Missing client identification` | `X-Client-Key` header is absent |
7070+| 401 | `DPoP authorization scheme required` | `Authorization` header doesn't start with `DPoP ` |
7171+| 401 | `DPoP proof header required` | `DPoP` header is absent |
7272+| 401 | `token_expired` | The access token has expired |
7373+| 401 | `Invalid client` | `X-Client-Key` doesn't match a known client |
7474+| 403 | `Child clients cannot create API clients` | The calling client is itself a child |
7575+| 403 | `Parent client owner not found` | The parent client's `created_by` DID is not in the `users` table |
7676+| 409 | `client_id_url already registered` | Another client already uses that `client_id_url` |
7777+7878+## Operational notes
7979+8080+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.
8181+8282+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.