Skip to content

API Endpoints

API endpoints implemented as Cloudflare Pages Functions. Sync endpoints use D1 database with client-side encryption.

POST /api/feeds

Fetch developer news feeds from multiple sources. Returns items grouped by source for client-side curation. No auth required.

Request:

{
  "languages": ["JavaScript", "Rust"],
  "frameworks": ["React"],
  "tools": ["Docker"],
  "topics": ["AI/ML & LLMs"],
  "resolvedMappings": {
    "Homelab": {
      "subreddits": ["selfhosted", "homelab"],
      "lobstersTags": ["selfhosted"],
      "devtoTags": ["homelab"]
    }
  }
}

Response:

{
  "feeds": {
    "hn": [{ "title": "...", "url": "...", "score": 100, "source": "HN" }],
    "lobsters": [{ "title": "...", "url": "...", "score": 50, "source": "Lobsters" }],
    "reddit": [{ "title": "...", "url": "...", "score": 200, "source": "r/rust" }],
    "github": [{ "title": "...", "url": "...", "score": 1000, "source": "GitHub (Rust)" }],
    "devto": [{ "title": "...", "url": "...", "score": 30, "source": "Dev.to" }]
  }
}

The client curates HN/Lobsters items via Haiku before including in the prompt. Reddit, GitHub, and Dev.to are already profile-targeted.


Sync Endpoints

The following endpoints support the sync system.

POST /api/profile/create

Create or re-upload a profile. Used for initial share.

Request:

{
  "id": "uuid",
  "name": "Profile Name",
  "password_hash": "salt:hash",
  "encrypted_api_key": "base64...",
  "salt": "base64...",
  "languages": ["JavaScript"],
  "frameworks": ["React"],
  "tools": ["Docker"],
  "topics": ["AI/ML"],
  "depth": "standard",
  "custom_focus": "optional text"
}

GET /api/profile/{id}?password_hash=...

Get full profile data including encrypted API key. Requires password.

Response:

{
  "id": "uuid",
  "name": "Profile Name",
  "encrypted_api_key": "base64...",
  "salt": "base64...",
  "languages": [],
  "frameworks": [],
  "tools": [],
  "topics": [],
  "depth": "standard",
  "custom_focus": null
}

GET /api/profile/{id}/status

Check if profile exists and get content hashes. No auth required.

Response:

{
  "exists": true,
  "diffs_hash": "hex...",
  "stars_hash": "hex...",
  "content_updated_at": "2026-01-26T04:13:00Z"
}

POST /api/profile/{id}/content

Download all encrypted content. Requires password.

Request:

{ "password_hash": "salt:hash" }

Response:

{
  "diffs": [{ "id": "uuid", "encrypted_data": "base64..." }],
  "stars": [{ "id": "uuid", "encrypted_data": "base64..." }],
  "salt": "base64...",
  "profile": {
    "name": "...",
    "languages": [],
    "frameworks": [],
    "tools": [],
    "topics": [],
    "depth": "standard",
    "custom_focus": null
  }
}

POST /api/profile/{id}/sync

Upload content and deletions. Requires password.

Request:

{
  "password_hash": "salt:hash",
  "diffs": [{ "id": "uuid", "encrypted_data": "base64 or JSON" }],
  "stars": [{ "id": "uuid", "encrypted_data": "base64..." }],
  "deleted_diff_ids": ["uuid"],
  "deleted_star_ids": ["uuid"],
  "diffs_hash": "hex...",
  "stars_hash": "hex...",
  "profile": { "name": "...", "languages": [], ... }
}

Public Diffs

For public diffs, encrypted_data contains plaintext JSON (starts with {) instead of an encrypted base64 blob. The server detects this format and serves public diffs via /api/diff/{id}/public.

## `GET /api/diff/{id}/public`

Retrieve a publicly shared diff. Returns 404 if the diff doesn't exist or is private. See [Public Diff Sharing](public-diff-sharing.md) for details. **No auth required.**

**Response:**
```json
{
  "id": "abc123",
  "content": "# Your Dev Digest\n\n...",
  "title": "Weekly Update",
  "generated_at": "2026-01-28T10:00:00Z",
  "profile_name": "Chris"
}

Cache Headers: Cache-Control: public, max-age=86400


GET /api/share/{id}

Get public profile info for import flow. No auth required.

Response:

{
  "id": "uuid",
  "name": "Profile Name",
  "languages": [],
  "frameworks": [],
  "tools": [],
  "topics": [],
  "depth": "standard",
  "password_salt": "base64..."
}