Admin API

Lexicons

Manage lexicons and network lexicons. See the Lexicons guide for background on how lexicons drive indexing and XRPC routing.

const TOKEN = "hv_..."; // your API key
const headers = { Authorization: `Bearer ${TOKEN}` };

Upload / upsert a lexicon

POST /admin/lexicons
interface LexiconResult {
  id: string;
  revision: number;
  backfill_job_id: string | null;
}

const response = await fetch("http://127.0.0.1:3000/admin/lexicons", {
  method: "POST",
  headers: {
    ...headers,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    lexicon_json: {
      lexicon: 1,
      id: "xyz.statusphere.status",
      defs: {
        main: {
          type: "record",
          key: "tid",
          record: {
            type: "object",
            required: ["status", "createdAt"],
            properties: {
              status: { type: "string", maxGraphemes: 1 },
              createdAt: { type: "string", format: "datetime" },
            },
          },
        },
      },
    },
    backfill: true,
    target_collection: null,
  }),
});
const data: LexiconResult = await response.json();
FieldTypeRequiredDescription
lexicon_jsonobjectyesRaw lexicon JSON (must have lexicon: 1 and a valid NSID id)
backfillbooleannoWhether the first upload triggers a historical backfill (default true)
target_collectionstringnoFor query/procedure lexicons, the record collection they operate on
token_costintegernoToken cost for query/procedure endpoints (overrides instance default)

The id must be a valid NSID — it is the lexicon's primary key, the XRPC method it is served at, and, for record lexicons, the collection HappyView filters Jetstream on. An id that is empty or malformed is rejected with 400 Bad Request.

Response: 201 Created (new) or 200 OK (upsert)

{
  "id": "xyz.statusphere.status",
  "revision": 1,
  "backfill_job_id": "9f1c8e2a-7b40-4d1e-9f3a-2c5b8d6e1a04"
}

backfill_job_id is the id of the backfill job this upload started, or null if it started none — because backfill was false, because this was an upsert rather than a first upload, because the lexicon is not a record type, or because the caller lacks backfill:create. Poll it via GET /admin/backfill/status.

List lexicons

GET /admin/lexicons
interface Lexicon {
  id: string;
  revision: number;
  lexicon_type: string;
  backfill: boolean;
  created_at: string;
  updated_at: string;
}

const response = await fetch("http://127.0.0.1:3000/admin/lexicons", {
  headers,
});
const data: Lexicon[] = await response.json();

Response: 200 OK

[
  {
    "id": "xyz.statusphere.status",
    "revision": 1,
    "lexicon_type": "record",
    "backfill": true,
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-01T00:00:00Z"
  }
]

Get a lexicon

GET /admin/lexicons/{id}
const response = await fetch(
  "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status",
  { headers },
);
const data = await response.json();

Response: 200 OK with full lexicon details including raw JSON.

Delete a lexicon

DELETE /admin/lexicons/{id}
const response = await fetch(
  "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status",
  {
    method: "DELETE",
    headers,
  },
);

Response: 204 No Content

Network Lexicons

Network lexicons are fetched from the atproto network via DNS TXT resolution and kept updated via the Jetstream subscription. See Lexicons - Network lexicons for background.

Add a network lexicon

POST /admin/network-lexicons
interface NetworkLexiconResult {
  nsid: string;
  authority_did: string;
  revision: number;
}

const response = await fetch("http://127.0.0.1:3000/admin/network-lexicons", {
  method: "POST",
  headers: {
    ...headers,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    nsid: "xyz.statusphere.status",
    target_collection: null,
  }),
});
const data: NetworkLexiconResult = await response.json();
FieldTypeRequiredDescription
nsidstringyesThe NSID of the lexicon to watch
target_collectionstringnoFor query/procedure lexicons, the record collection they operate on

HappyView resolves the NSID authority via DNS TXT, fetches the lexicon from the authority's PDS, parses it, and stores it.

Response: 201 Created

{
  "nsid": "xyz.statusphere.status",
  "authority_did": "did:plc:authority",
  "revision": 1
}

List network lexicons

GET /admin/network-lexicons
interface NetworkLexicon {
  nsid: string;
  authority_did: string;
  target_collection: string | null;
  last_fetched_at: string;
  created_at: string;
}

const response = await fetch("http://127.0.0.1:3000/admin/network-lexicons", {
  headers,
});
const data: NetworkLexicon[] = await response.json();

Response: 200 OK

[
  {
    "nsid": "xyz.statusphere.status",
    "authority_did": "did:plc:authority",
    "target_collection": null,
    "last_fetched_at": "2025-01-01T00:00:00Z",
    "created_at": "2025-01-01T00:00:00Z"
  }
]

Remove a network lexicon

DELETE /admin/network-lexicons/{nsid}
const response = await fetch(
  "http://127.0.0.1:3000/admin/network-lexicons/xyz.statusphere.status",
  {
    method: "DELETE",
    headers,
  },
);

Removes the network lexicon tracking and also deletes the lexicon from the happyview_lexicons table and in-memory registry.

Response: 204 No Content