Skip to content

Main App API

Internal REST API for the main app (Bun + Elysia). Authenticated routes require a signed did cookie.

For the AT Protocol XRPC endpoints, see XRPC API.


Redirects to the AT Protocol OAuth authorize URL.

  • 302 → OAuth URL
  • 302 → /?error=missing_handle if no handle provided
  • 302 → /?error=auth_failed on failure
{ "url": "https://..." }

On failure: { "error": "Authentication failed", "details": "..." }

  • 302 → /onboarding (new user)
  • 302 → /editor (returning user)
  • 302 → /?error=auth_failed on failure
{ "success": true }
{ "authenticated": true, "did": "did:plc:..." }
{ "authenticated": false }

{
"did": "did:plc:...",
"hasSites": true,
"hasDomain": false,
"domain": null,
"sitesCount": 3
}
{ "did": "did:plc:...", "handle": "user.bsky.social" }

Each site carries its own domains, so listing sites and their domains is a single request.

{ "sites": [ { "did": "did:plc:...", "rkey": "site-rkey", "display_name": "site-rkey", "created_at": 0, "updated_at": 0, "domains": [ /* domain rows */ ] } ] }
{
"wispDomains": [{ "domain": "name.wisp.place", "rkey": "site-rkey" }],
"customDomains": [ /* custom domain rows */ ]
}

Lists the caller’s place.wisp.fs records on their PDS. Sites missing from site_cache get a placeholder row (no files, cold_synced = false) so they are listed immediately, and every missing, stale or not-yet-downloaded site is queued for the firehose service’s revalidation worker. synced counts records found on the PDS, queued the sites handed to the worker. The dashboard calls this on load and sign-in runs it too.

{ "success": true, "synced": 2, "queued": 1, "errors": [] }

Domains for one site. GET /api/user/sites already includes these, so prefer it when listing.

{ "rkey": "site-rkey", "domains": [ /* domain rows */ ] }

{ "available": true, "domain": "name.wisp.place" }
{ "available": false, "reason": "invalid" }
{ "registered": true, "type": "wisp", "domain": "name.wisp.place", "did": "did:plc:...", "rkey": "site-rkey" }
{ "registered": true, "type": "custom", "domain": "example.com", "did": "did:plc:...", "rkey": "site-rkey", "verified": true }
{ "registered": false }

Caddy’s on-demand TLS asks this endpoint, so a custom domain answers registered: true only once it is verified; an unverified claim answers 404.

{ "success": true, "domain": "name.wisp.place" }
{ "success": true, "domain": "name.wisp.place" }
{ "success": true, "id": "abcdef1234567890", "domain": "example.com", "verified": false }
{ "success": true, "verified": true, "error": null, "found": true }
{ "success": true }
{ "success": true }
{ "success": true }
{ "success": true }

{ "success": true, "message": "Site deleted successfully" }

On failure: { "success": false, "error": "..." }

Returns the place.wisp.settings record or defaults:

{ "indexFiles": ["index.html"], "cleanUrls": false, "directoryListing": false }
{ "success": true, "uri": "at://...", "cid": "bafy..." }

On failure: { "success": false, "error": "Only one of spaMode, directoryListing, or custom404 can be enabled" }


{ "success": true, "jobId": "...", "message": "Upload started. Connect to /wisp/upload-progress/..." }

Empty upload: { "success": true, "uri": "at://...", "cid": "bafy...", "fileCount": 0, "siteName": "my-site" }

Server-sent events stream:

  • progress → { status, progress, result, error }
  • done → result
  • error → { error }