Arabica JSON API Contract #
This directory is the interface contract between three parallel work streams in the SvelteKit SPA migration:
- Stream A (Go JSON API) implements these endpoints.
- Stream B (SvelteKit frontend) consumes them.
- Stream C (testing) generates contract tests from these shapes.
Each file documents the request/response shapes for a domain area. When adding or changing an endpoint, update the spec here first, then implement.
The non-JSON homepage Open Graph contract covers crawler metadata and the public preview image endpoint.
Conventions #
-
All JSON endpoints return
Content-Type: application/json; charset=utf-8. -
Mutations (POST/PUT/DELETE) are CSRF-protected via the
CrossOriginProtectionmiddleware. The SvelteKit frontend sends the same cookies/forms it does today. -
Error responses use a stable envelope with an appropriate HTTP status:
{ "error": "Human-readable safe message", "code": "stable_machine_code" }Validation responses may also include
fields, a map from field name to a safe validation message. Machine codes are low-cardinality values such asauthentication_required,session_expired,permission_denied,not_found,validation_failed,invalid_request,conflict,rate_limited,service_unavailable, andinternal_error. -
Error messages must not expose dependency errors, stack traces, tokens, session identifiers, or other internal details.
-
Timestamps are RFC 3339 strings.
-
AT-URIs are full
at://did:plc:.../collection/rkeystrings. -
Pagination uses cursor strings; an empty
next_cursormeans no more pages.
Existing JSON Endpoints (already working) #
These endpoints already return JSON and need no changes:
| Endpoint | Method | Response shape |
|---|---|---|
/.well-known/oauth-client-metadata.json |
GET | OAuth client metadata |
/healthz |
GET | {status, firehose, feed_index} |
/api/resolve-handle |
GET | {did, handle, displayName?, avatar?} |
/api/search-actors |
GET | {actors: [...]} |
/api/suggestions/{entity} |
GET | []EntitySuggestion |
/api/data |
GET | {did, beans[], roasters[], ...} |
/api/recipes |
GET | []Recipe |
/api/recipes/suggestions |
GET | []Recipe |
/api/recipes/{id} |
GET | Recipe |
/api/recipes |
POST | Recipe |
/api/recipes/from-brew/{id} |
POST | Recipe |
/api/recipes/fork/{id} |
POST | Recipe |
/api/{entity} |
POST | <model> (simple entities) |
/api/{entity}/{id} |
PUT | <model> or HX-Redirect |
/brews/export |
GET | []Brew |
Phase 1 Endpoints (implemented) #
These endpoints were added in Phase 1 of the SvelteKit migration:
| Endpoint | Method | Response shape | Spec |
|---|---|---|---|
/api/feed |
GET | {items[], next_cursor, is_authenticated, query} |
feed.md |
/api/beans/{actor}/{id} |
GET | {record, subject_uri, social, backlinks, ...} |
entities.md |
/api/roasters/{actor}/{id} |
GET | {record, subject_uri, social, backlinks, ...} |
entities.md |
/api/grinders/{actor}/{id} |
GET | {record, subject_uri, social, backlinks, ...} |
entities.md |
/api/brewers/{actor}/{id} |
GET | {record, subject_uri, social, backlinks, ...} |
entities.md |
/api/brews/{actor}/{id} |
GET | {record, subject_uri, social, backlinks, ...} |
entities.md |
/api/recipes/{actor}/{id} |
GET | {record, subject_uri, social, backlinks, ...} |
entities.md |
/api/brews |
POST | {brew, incomplete_nudge?} (JSON) or HX-Redirect |
brews.md |
/brews/{id} |
PUT | {brew} (JSON) or HX-Redirect |
brews.md |
/api/manage |
GET | {did, beans[], roasters[], ..., stats} |
manage.md |
/api/brews |
GET | {brews[], has_more, next_offset} |
brew_list.md |
/api/profile/{actor} |
GET | {profile, brews[], beans[], ..., stats} |
profile.md |
/api/notifications |
GET | {notifications[], next_cursor} |
notifications.md |
/api/notifications/read |
POST | {read: true} (JSON) or redirect |
notifications.md |
/api/explore |
GET | {items[], documents, facet_counts, next_cursor, health} |
explore.md |
/api/likes/toggle |
POST | {is_liked, like_count, subject_uri} (JSON) or HTML |
social.md |
/api/comments |
GET | {comments[], subject_uri, is_authenticated} (JSON) or HTML |
social.md |
/api/comments |
POST | {comment, comments[]} (JSON) or HTML |
social.md |
/api/comments/{id} |
DELETE | {deleted: true} (JSON) or HX-Trigger |
social.md |
/api/report |
POST | {report_id, submitted} (JSON) or HTML |
social.md |
/api/settings |
GET | {profile_stats_visibility, user_preferences, bluesky_profile} |
settings.md |
/api/settings/preferences |
POST | {saved: true} (JSON) or HTML |
settings.md |
/api/settings/profile-visibility |
POST | {saved: true} (JSON) or HTML |
settings.md |
/api/onboarding |
GET | {readiness, beans[], brewers[], ...} |
onboarding.md |
/api/incomplete-records |
GET | {records[]} (JSON) or HTML |
onboarding.md |
/api/popular-recipes |
GET | [Recipe] (JSON) or HTML |
onboarding.md |
/api/{entity}/{actor}/{id}/backlinks |
GET | {entity_noun, entity_name, result} |
backlinks.md |
/api/_mod |
GET | {hidden_records, reports, audit_log, ...} |
admin.md |
/api/_mod/stats |
GET | {stats, backups} |
admin.md |
/_mod/* |
POST | {ok, action, message} (JSON) or HX-Trigger |
admin.md |
/api/signup/categories |
GET | {categories: [{title, description, providers: [...], dev_only}]} |
P1.14 |
Endpoints to Add (Phase 1) #
See the migration plan (docs/plans/2026-07-08-sveltekit-migration.md) for
the full list and priority order. Each endpoint's shape is documented in
its own file as it is implemented:
feed.md—GET /api/feed(JSON feed items) [done]entities.md—GET /api/{entity}/{actor}/{id}(entity view data) [done]brews.md—POST /brews,PUT /brews/{id}(brew mutations) [done]manage.md—GET /api/manage(records + stats) [done]brew_list.md—GET /api/brews(paginated brew list) [done]profile.md—GET /api/profile/{actor}(profile data bundle) [done]notifications.md—GET /api/notifications[done]explore.md—GET /api/explore[done]social.md— likes, comments, reports (JSON mutations) [done]settings.md—GET /api/settings, settings mutations [done]onboarding.md—GET /api/onboarding, incomplete records [done]backlinks.md—GET /api/{entity}/{actor}/{id}/backlinks[done]admin.md— moderation admin endpoints [done]signup.md—GET /api/signup/categories(PDS provider catalog) [done]