Manage the domains a HappyView instance serves. Each domain gets its own atproto OAuth client identity. The primary domain follows PUBLIC_URL: every start makes the domain with that URL primary, adding it on the first start. All endpoints require the settings:manage permission or the platform’s key.
On an instance run by a managed-hosting platform (one with PLATFORM_API_KEY_HASH set, which GET /config reports as platform_managed), the platform decides which hostnames reach the instance and which one is its public URL. There, adding, deleting and making a domain primary accept only the platform’s key, and answer 403 to everyone else, an operator with superadmin included. Listing domains still accepts settings:manage.
const TOKEN = "hv_..."; // your API keyconst headers = { Authorization: `Bearer ${TOKEN}` };const TOKEN = "hv_..."; // your API keyconst headers = { Authorization: `Bearer ${TOKEN}` };let token = "hv_..."; // your API keyconst token = "hv_..." // your API key# All examples assume $TOKEN is an API key (hv_...)AUTH="Authorization: Bearer $TOKEN"List domains
Section titled “List domains”GET /admin/domainsinterface Domain { id: string; url: string; is_primary: boolean; created_at: string; updated_at: string;}
const response = await fetch("http://127.0.0.1:3000/admin/domains", { headers,});const data: Domain[] = await response.json();const response = await fetch("http://127.0.0.1:3000/admin/domains", { headers,});const data = await response.json();let client = reqwest::Client::new();let response = client .get("http://127.0.0.1:3000/admin/domains") .bearer_auth(token) .send() .await?;let data: serde_json::Value = response.json().await?;req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/domains", nil)req.Header.Set("Authorization", "Bearer "+token)resp, err := http.DefaultClient.Do(req)if err != nil { log.Fatal(err)}defer resp.Body.Close()curl http://127.0.0.1:3000/admin/domains -H "$AUTH"Response: 200 OK
[ { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://gamesgamesgamesgames.games", "is_primary": true, "created_at": "2026-04-16T00:00:00Z", "updated_at": "2026-04-16T00:00:00Z" }]Add a domain
Section titled “Add a domain”POST /admin/domainsinterface Domain { id: string; url: string; is_primary: boolean; created_at: string; updated_at: string;}
const response = await fetch("http://127.0.0.1:3000/admin/domains", { method: "POST", headers: { ...headers, "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://api.example.com", }),});const data: Domain = await response.json();const response = await fetch("http://127.0.0.1:3000/admin/domains", { method: "POST", headers: { ...headers, "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://api.example.com", }),});const data = await response.json();let client = reqwest::Client::new();let response = client .post("http://127.0.0.1:3000/admin/domains") .bearer_auth(token) .json(&serde_json::json!({ "url": "https://api.example.com" })) .send() .await?;let data: serde_json::Value = response.json().await?;body := bytes.NewBufferString(`{"url": "https://api.example.com"}`)req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/domains", body)req.Header.Set("Authorization", "Bearer "+token)req.Header.Set("Content-Type", "application/json")resp, err := http.DefaultClient.Do(req)if err != nil { log.Fatal(err)}defer resp.Body.Close()curl -X POST http://127.0.0.1:3000/admin/domains \ -H "$AUTH" \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.example.com" }'| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | An origin: scheme, host and optional port, with no path. A trailing slash is dropped, and the scheme and host are lowercased. Must be https unless PUBLIC_URL is a loopback address. |
Returns 400 Bad Request if the URL is invalid, has a path or no host, isn’t https where that’s required, or is already registered.
Response: 201 Created
{ "id": "550e8400-e29b-41d4-a716-446655440001", "url": "https://api.example.com", "is_primary": false, "created_at": "2026-04-16T00:00:00Z", "updated_at": "2026-04-16T00:00:00Z"}Also builds an OAuth client for the domain and updates the in-memory cache.
Remove a domain
Section titled “Remove a domain”DELETE /admin/domains/{id}await fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001", { method: "DELETE", headers,});await fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001", { method: "DELETE", headers,});let client = reqwest::Client::new();client .delete("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001") .bearer_auth(token) .send() .await?;req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001", nil)req.Header.Set("Authorization", "Bearer "+token)resp, err := http.DefaultClient.Do(req)if err != nil { log.Fatal(err)}defer resp.Body.Close()curl -X DELETE http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001 \ -H "$AUTH"Returns 400 Bad Request if the domain is the primary domain (make another domain primary first), or if it’s the instance’s only domain. Returns 404 Not Found if the domain doesn’t exist.
Response: 204 No Content
Also removes the domain’s OAuth client and cache entry.
Set primary domain
Section titled “Set primary domain”POST /admin/domains/{id}/primaryawait fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary", { method: "POST", headers,});await fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary", { method: "POST", headers,});let client = reqwest::Client::new();client .post("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary") .bearer_auth(token) .send() .await?;req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary", nil)req.Header.Set("Authorization", "Bearer "+token)resp, err := http.DefaultClient.Do(req)if err != nil { log.Fatal(err)}defer resp.Body.Close()curl -X POST http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary \ -H "$AUTH"Sets the target domain as the primary. Unsets the current primary in a single operation. Returns 404 Not Found if the domain doesn’t exist. The change lasts until the next start, which makes the domain matching PUBLIC_URL primary again, so change PUBLIC_URL too.
Response: 204 No Content
Also updates the in-memory cache and primary client reference.
© HappyProto

