COVENANT · API v0.1.0  ·  get covenant →

forge.covenant.uno — activity scoreboard backend. Counts prompt and commit events per user; no payloads, no code, no output ever leave the client. Auth is a Covenant JWT (minted from a GitHub token) sent as Authorization: Bearer <jwt>.


public

GET/healthz

Liveness probe. Used by the platform health check.

response · 200 text/plain
ok
public

GET/  ·  GET/docs  ·  GET/docs/executors

This animated status page, and these docs. HTML, no auth.
Guide: executor capability matrix → — which executor supports skills/agents/commands/hooks/mcps/memory, and where each lives.


auth
public

POST/auth/exchange

Exchange a GitHub OAuth access token for a Covenant JWT. Verifies the token against the GitHub API, upserts the user (github_id, login, avatar), and mints a signed JWT. The desktop app calls this once at sign-in and stores the JWT in the macOS Keychain.

body
{ "github_access_token": "gho_..." }
response · 200
{
  "jwt":        "eyJ...",        // Bearer token for all authed routes
  "login":      "karluiz",
  "avatar_url": "https://...",
  "github_id":  12345678
}
errors
401  GitHub token invalid / expired

sync · push-only telemetry
JWT

POST/sync/events

Push a batch of activity events. Idempotent: deduped on (github_id, dedupe_key) where dedupe_key = client_ts_ms:kind:executor, so re-sending the same batch never double-counts. Max 2000 events/request (the client batches 500). Events carry metadata only — never prompt text, diffs, or output.

body
{
  "events": [
    {
      "client_ts_ms": 1717800000000,   // client clock, ms
      "kind":         "prompt",         // "prompt" | "commit"
      "executor":     "claude",         // claude|codex|copilot|pi|hermes
      "day":          "2026-06-07",     // YYYY-MM-DD, client local tz
      "repo":         "karlTerminal",   // optional
      "branch":       "main",            // optional
      "group_name":   "covenant"        // optional
    }
  ]
}
response · 200
{
  "inserted":        37,            // rows actually written (post-dedupe)
  "server_cursor_ms": 1717800050123  // server clock after write
}
errors
400  > 2000 events, or bad "kind"
401  missing / invalid JWT
JWT

GET/sync/cursor

How much the server holds for you. The client reads this to advance its local sync pointer and reconcile counts.

response · 200
{
  "server_cursor_ms": 1717800050123,  // MAX(server_ts_ms)
  "total_events":     1842            // COUNT(*) for this user
}

read · breakdowns (your own data)
JWT

GET/api/breakdown/repos?range=30d

Prompt + commit totals grouped by repo, ordered by prompts. range = 7d | 30d (default) | all.

response · 200
[ { "repo": "karlTerminal", "prompts": 412, "commits": 58 } ]
JWT

GET/api/breakdown/branches?repo=karlTerminal&range=30d

Same totals for one repo, grouped by branch (top 20). repo is required.

response · 200
[ { "branch": "main", "prompts": 210, "commits": 34 } ]
JWT

GET/api/breakdown/groups?range=30d

Prompt totals grouped by group/project name.

response · 200
[ { "group_name": "covenant", "prompts": 733 } ]
JWT

GET/api/sessions/recent?limit=10

Recent work sessions, reconstructed by gap-splitting events per (repo, branch): a > 15 min idle gap starts a new session. Ordered newest first.

response · 200
[ {
  "start_ts": 1717790000000, "end_ts": 1717793600000,
  "repo": "karlTerminal", "branch": "main", "group_name": "covenant",
  "prompts": 48, "commits": 6
} ]

public profile
public

GET/u/:login

Public HTML activity profile for a user (rendered server-side).

public

GET/u/:login/json

Same profile as JSON: lifetime totals, today's count, current streak, and the per-day cells used to draw the heatmap.

response · 200
{
  "login": "karluiz", "avatar_url": "https://...",
  "total_prompts": 9120, "total_commits": 1043,
  "today_prompts": 61,  "current_streak": 23,
  "cells": [ { "day": "2026-06-07", "prompts": 61, "commits": 8 } ]
}