Skip to content

Diff Service API Reference

Base URL

  • Production: https://diff.scrymore.com
  • Stage: https://diff-stage.scrymore.com

Authentication

Every route except GET /healthz requires:

http
Authorization: Bearer <SERVICE_AUTH_TOKEN>

Only the dashboard server holds this token. Browser traffic goes through the dashboard's /api/diff proxy, which is where per-user authorization happens.

Pairs

RoutePurpose
GET /healthzLiveness. The only open route
POST /api/pairsRegister or replace a pair. Idempotent — re-posting reopens the review and preserves issues
GET /api/pairs/:pairIdThe pair, plus has_issues_count, candidate_count and review state
GET /image/:pairId/:slotStream slot a (Figma reference) or b (Storybook build)

A pair registration carries R2 keys, never image bytes:

json
{
  "pair_id": "…", "project_id": "…", "link_id": "…", "name": "…",
  "image_a_key": "…", "image_b_key": "…",
  "figma_structure_key": "…", "dom_structure_key": "…", "source_key": "…",
  "icon_candidates_key": "…", "icon_contact_sheet_key": "…"
}

The evidence keys are optional and unlock better analysis — see Review Model.

Issues

RoutePurpose
GET /api/issues?pair=List issues for a pair
POST /api/issuesCreate one. Needs a note or at least one box
PATCH /api/issues/:idUpdate the note
DELETE /api/issues/:idDelete
POST /api/issues/:id/transitionMove through the lifecycle
PATCH /api/issues/:id/severityChange severity on a promoted issue

Boxes are {x, y, w, h} normalised 0..1. On list responses box_a/box_b come back as JSON strings or null, and labels as an array — shapes kept compatible with the reference implementation.

transition takes {action, who, ...} where action is promote | dismiss | restore | fix | verify | reopen. promote requires a severity; dismiss requires a reason.

Agent

RoutePurpose
POST /api/agent/annotateRun the structural pass over a pair; findings land as candidates
POST /api/agent/refineRewrite a rough reviewer note into developer-facing feedback
GET /api/pairs/:pairId/predictionExport the current AI result in the reference prediction format
GET /api/pairs/:pairId/runs/:runId/:artifactRead a run artifact

artifact is one of prediction, main-pass, icon-pass, raw-pass-findings, hybrid-report.

annotate returns {format, run_id, prediction, created, detected, refreshed, superseded, candidates, count, passes, artifacts} — the counts describe reconciliation against existing candidates, not just what was found.

Bulk

RoutePurpose
POST /api/pairs/:pairId/candidates/dismiss-bulk{ids? | max_confidence? | label?, reason, who}

This is the only bulk path. There is deliberately no bulk promote: promotion assigns severity and is a per-issue human decision.

Review queue

RoutePurpose
POST /api/claimAcquire or refresh the lock on a pair. force overrides
POST /api/releaseRelease the lock. No-op if not the holder
POST /api/doneSet or unset done. Marking done clears the lock
POST /api/done_nextMark done and return the next claimable pair
GET /api/next?who=&after=&project=Next claimable pair after after, wrapping
GET /api/queue?project={total, done, in_progress, open}
GET /api/state?project=Every pair's state for a project

Locks go stale after 180 seconds, after which anyone may claim them — a reviewer who closes their laptop mid-review does not block the queue. Traversal skips pairs that are done or live-locked by someone else.

Figma

RoutePurpose
GET /api/figma/oauth/startBegin the OAuth connection
GET/POST/DELETE /api/projects/:projectId/figma-connectionsManage connections
POST /api/projects/:projectId/figma-connections/:id/testVerify a connection still works
GET /api/projects/:projectId/figma/files/:fileKey/metaFile metadata
POST /api/projects/:projectId/figma/resolve-fileResolve a Figma URL to a file
GET/POST /api/projects/:projectId/figma-sourcesTracked source files
POST /api/projects/:projectId/design-snapshotsStart a snapshot import
POST /api/projects/:projectId/design-snapshots/:id/ingest-fileIngest a file into it
POST /api/projects/:projectId/design-snapshots/:id/completeFinish the snapshot
GET /api/projects/:projectId/design-screensImported screens
GET /api/projects/:projectId/design-screens/:screenId/imageStream a screen image
GET/PUT /api/projects/:projectId/dev-resourcesFigma Dev Mode resources

A connection is unique per (project_id, figma_user_id), and its token is encrypted with FIGMA_TOKEN_ENCRYPTION_KEY.

Released under the MIT License.