REST API
Everything the panel reads, the API reads: projects, sessions, messages and memories. Base URL: https://handoff-it.com/api — JSON in and out.
Authentication
Use an API key in the header (create one at Dashboard → API & MCP):
Authorization: Bearer hof_your_key_here
Example: search and read a conversation
# 1. Find the moment
curl "https://handoff-it.com/api/search/conversations?query=stripe+webhook" -H "Authorization: Bearer hof_YOUR_KEY"
# 2. Open the surrounding messages (hit carries sessionId + srcOffset)
curl "https://handoff-it.com/api/sessions/SESSION_ID/messages?fromOffset=OFFSET&direction=newer" -H "Authorization: Bearer hof_YOUR_KEY"
Endpoints
| Method | Route | Description |
|---|---|---|
POST | /api/ingest/sessions | Upserts one conversation (unique per user+tool+externalId) and answers the resume offset — the server is the source of truth for how far a session is archived. { "tool": "claude_code", "externalId": "…", "title?": "…", "cwd?": "…", "gitOriginUrl?": "…" } |
POST | /api/ingest/sessions/:id/chunks | Uploads one raw gzip chunk (body = gzip bytes; metadata in x-src-byte-start/-end, x-payload-sha256, x-line-count headers). Idempotent: identical re-uploads answer 200 duplicate; a grown same-start span is prefix-merged; a rewritten file gets 409 divergent_chunk. |
GET | /api/projects | Lists projects with session/memory counts and last activity. |
GET | /api/projects/:id/sessions | The project's sessions, newest first (cursor pagination). |
GET | /api/sessions/:id/messages | Parsed messages of one session, paged by srcOffset (?fromOffset&direction=older|newer&limit). |
GET | /api/chunks/:id | The raw (redacted) transcript text of one stored chunk — the deepest drill-down. |
GET | /api/search/conversations | Full-text search over parsed messages (?query&projectId&tool&limit). |
GET | /api/memories | Lists/searches saved memories (?q&tag&kind&projectId&limit&cursor). |
POST | /api/memories | Creates a memory (note or checkpoint) with optional project/session provenance. { "title": "…", "content": "…", "kind?": "checkpoint", "projectId?": "…", "tags?": ["…"] } |
GET | /api/me | Current plan and usage (storage, projects, sessions, memories). |
Errors
Every failure uses the same envelope:
{ "error": { "code": "quota_exceeded", "message": "Storage limit exceeded: 498.2 MB of 500 MB used on the Free plan..." } }
| Status | Code | When |
|---|---|---|
| 401 | unauthorized / invalid_api_key | Missing header, invalid or revoked key |
| 402 | quota_exceeded / plan_required | Plan quota reached or paid-plan feature |
| 404 | not_found | Resource doesn't exist or belongs to another user |
| 409 | divergent_chunk | Divergent chunk (source file rewritten) |
| 400 | validation_error | Invalid request body |
Limits
Storage (memory bodies + the compressed archive, shared per account) and seats vary by plan — see the plans. Ingest chunks up to 4 MB gzip.