43 — Registry API Contract
The HTTP API surface between the locara CLI, the website, the registry backend, and (eventually) the optional Locara Manager utility. This is the contract that lets multiple components be built independently against a shared interface.
This is v1 of the API. Breaking changes require an ADR + version bump (/v2/...).
Base URL
- Production:
https://registry.locara.app/v1/ - Staging:
https://registry-staging.locara.app/v1/ - Local development:
http://localhost:8787/v1/
All endpoints are HTTPS in production. Internal-network calls (CI → DB) skip TLS where appropriate.
Authentication
| Caller | Auth method |
|---|---|
| Public read (website, CLI lookups) | None — anonymous |
Publisher writes (locara publish, account management) | GitHub OAuth → JWT bearer token |
| CI (build pipeline) | GitHub Actions OIDC token verified by registry |
| Admin (trust group) | GitHub OAuth + maintainer-list check |
JWT tokens are short-lived (1 hour) and refreshed via standard OAuth refresh flow. Hardware-key-signed assertions optional for high-trust publishers.
Endpoints
Public read endpoints (no auth)
GET /apps
List apps in the catalog.
GET /v1/apps?category=productivity&fully_local=true&page=1&limit=50
Response:
{
"apps": [
{
"id": "kingtongchoo/transcribe",
"publisher": "kingtongchoo",
"name": "transcribe",
"latest_version": "0.1.0",
"display_name": "Transcribe",
"description": "Fully-local audio and meeting transcription.",
"category": "productivity",
"tags": ["audio", "transcription"],
"icon_url": "https://cdn.locara.app/apps/kingtongchoo/transcribe/0.1.0/icon.png",
"fully_local": true,
"device_fit": { "min_ram_gb": 8 },
"stats": {
"installs": 142,
"rating_avg": 4.7,
"rating_count": 23
},
"verified_publisher": "domain"
}
],
"page": 1,
"limit": 50,
"total": 27
}
Filters: category, fully_local, device_fit_max_ram, verified_publisher, tag, q (full-text search).
GET /apps/{publisher}/{name}
Single app’s full record.
Response:
{
"id": "kingtongchoo/transcribe",
"publisher": "kingtongchoo",
"name": "transcribe",
"latest_version": "0.1.0",
"versions": ["0.1.0"],
"manifest": { /* full locara.json */ },
"long_description_md": "...",
"screenshots": ["https://cdn.locara.app/...", "..."],
"homepage": "https://...",
"repository": "https://github.com/...",
"license": "Apache-2.0",
"publisher_verified": "domain",
"first_published_at": "2026-05-15T10:00:00Z",
"stats": { ... }
}
GET /apps/{publisher}/{name}/{version}
Specific version’s record.
{
"id": "kingtongchoo/transcribe@0.1.0",
"manifest": { /* */ },
"lockfile": { /* */ },
"artifact": {
"url": "https://cdn.locara.app/apps/kingtongchoo/transcribe/0.1.0.dmg",
"sha256": "abc...",
"size_bytes": 1234567890
},
"provenance": {
"build_url": "https://github.com/...",
"commit_sha": "def...",
"sigstore_log_id": "ghi..."
},
"review": {
"status": "approved",
"auto": true,
"reviewed_at": "2026-05-15T10:08:23Z"
},
"published_at": "2026-05-15T10:08:23Z"
}
GET /apps/{publisher}/{name}/latest
Convenience: latest version’s record (same shape as version-specific). Used by app updaters.
GET /revocations
List of revoked app versions (kill-switch). Apps poll this on launch.
{
"revocations": [
{
"publisher": "baduser",
"name": "evil-app",
"version": "0.5.2",
"reason": "malware",
"revoked_at": "2026-06-01T14:20:00Z",
"signature": "..."
}
],
"list_signature": "...", // Locara-signed, allows clients to verify integrity
"list_updated_at": "2026-06-01T14:20:00Z"
}
The list is paginated (/revocations?since=<timestamp> for incremental sync). Apps cache the full list locally + sync deltas.
GET /models
List of curated model manifests.
GET /models/{model-id}
Single curated model manifest (with HF artifact URL + recommended params).
GET /tools and GET /tools/{tool-id}
Curated wasm tool registry.
GET /components and GET /components/{component-id}
Curated component registry (shadcn-style).
Publisher endpoints (require auth)
POST /auth/oauth/start
Start GitHub OAuth flow. Redirects to GitHub.
POST /auth/oauth/callback
OAuth callback. Returns JWT.
GET /me
Current publisher account.
{
"id": "kingtongchoo",
"github_username": "kingtongchoo",
"verified_domain": "kingtongchoo.dev",
"publisher_signing_pubkey": "ed25519:...",
"apps_published": 2,
"two_fa_enabled": true,
"created_at": "2026-04-01T00:00:00Z"
}
PATCH /me
Update account info (display name, contact email, etc.).
POST /me/verify-domain
Initiate DNS-TXT verification for the “domain-verified” badge.
// Request
{ "domain": "kingtongchoo.dev" }
// Response
{
"verification_token": "locara-verify-abc123def456",
"expected_dns_record": {
"type": "TXT",
"name": "_locara-verify.kingtongchoo.dev",
"value": "abc123def456"
},
"expires_at": "2026-05-08T00:00:00Z"
}
Then POST /me/verify-domain/check to verify the DNS record exists.
POST /apps/submit
Initiate a new app submission. Triggers CI build.
// Request
{
"repo_url": "https://github.com/kingtongchoo/transcribe-locara",
"commit_sha": "abc123def456...",
"manifest_signature": "...", // Ed25519 signature of the manifest by publisher's signing key
"expected_version": "0.1.0" // matches the manifest's version field
}
// Response
{
"submission_id": "sub_abc123",
"status": "queued",
"ci_url": "https://github.com/locara/ci-runs/...",
"estimated_completion": "2026-05-01T10:15:00Z"
}
Server-side: validates the publisher owns the repo (or is granted permission), kicks off GitHub Actions, returns submission ID. CI updates submission status as it progresses.
GET /submissions/{submission_id}
Check submission status.
{
"id": "sub_abc123",
"publisher": "kingtongchoo",
"app_id": "kingtongchoo/transcribe",
"version": "0.1.0",
"status": "building",
"progress": [
{ "step": "verify", "status": "complete", "duration_ms": 4200 },
{ "step": "test", "status": "complete", "duration_ms": 38000 },
{ "step": "build", "status": "in_progress" },
{ "step": "sign", "status": "pending" },
{ "step": "notarize", "status": "pending" },
{ "step": "review", "status": "pending" }
],
"ci_url": "https://github.com/locara/ci-runs/...",
"error": null
}
GET /me/submissions
List the publisher’s submissions.
POST /apps/{publisher}/{name}/unpublish
Mark a version (or all versions) as unpublished. Doesn’t revoke (which is more aggressive); just removes from public catalog.
{
"versions": ["0.1.0"], // or "all"
"reason": "discontinued"
}
The artifact remains in the registry for users who already installed; new installs from /apps/... return 410 Gone.
Reviews + ratings (require auth)
POST /apps/{publisher}/{name}/reviews
Leave a review.
{
"version": "0.1.0",
"rating": 5,
"title": "Works exactly as advertised",
"body": "..."
}
Server requires verifiable install (anti-spam): user must have installed the app in question (proven via signed install record from the runtime).
GET /apps/{publisher}/{name}/reviews
Public reviews list.
{
"reviews": [
{
"id": "rev_abc",
"rating": 5,
"title": "...",
"body": "...",
"version_reviewed": "0.1.0",
"author_username": "alice42",
"author_verified_install": true,
"created_at": "2026-06-01T10:00:00Z"
}
]
}
POST /reviews/{review_id}/helpful
Vote a review as helpful. Auth-required to prevent spam.
Admin endpoints (trust group only)
POST /admin/revoke
Revoke a published app version. Requires trust-group auth + reason.
{
"publisher": "baduser",
"name": "evil-app",
"versions": ["0.5.2"], // or "all"
"reason": "malware",
"public_advisory_url": "https://locara.app/security/2026-06-01-baduser-evil-app",
"signature": "..." // multi-party signature for high-impact revocations
}
Effect: appears in /revocations immediately; affected apps refuse to launch on next start.
POST /admin/review/{submission_id}
Approve or reject a queued submission.
{
"decision": "approved" | "rejected" | "needs-changes",
"reviewer_notes": "...",
"signature": "..."
}
GET /admin/submissions/queue
Submissions awaiting human review.
POST /admin/publishers/{publisher_id}/suspend
Suspend a publisher account (e.g., during compromise investigation).
POST /admin/audit-log/append
Internal admin actions are logged to an append-only audit trail.
CI endpoints (CI service auth only)
POST /ci/submission/{id}/status
CI updates submission status as it progresses through build / sign / notarize / attest.
POST /ci/submission/{id}/upload
CI uploads the signed artifact + provenance attestation.
POST /ci/submission/{id}/complete
CI declares the build complete; triggers review queue.
Data formats
Errors
All errors:
{
"error": {
"code": "validation_failed",
"message": "Manifest schema is invalid",
"details": [
{ "field": "version", "issue": "must be valid semver" }
],
"request_id": "req_abc123"
}
}
Error codes:
unauthenticatedunauthorizedvalidation_failednot_foundconflict(e.g., version already exists)rate_limitedsubmission_failedinternal_error
Pagination
Cursor-based for stability:
?cursor=eyJpZCI6...
Response includes next_cursor if more results exist.
Versioning
API version is in the URL path (/v1/...). Breaking changes go to /v2/.... Both versions supported in parallel for at least 12 months after /v2/ ships.
Rate limits
Anonymous: 60 req/min/IP. Authenticated: 600 req/min/account. CI: high (500 req/sec aggregate).
429 returned with Retry-After header.
Webhook subscriptions (publishers)
Publishers can subscribe to webhooks for events relevant to their apps.
POST /me/webhooks
{
"url": "https://my-build-system/webhook",
"events": ["submission.completed", "review.decided", "revocation.applied"],
"signing_secret": "..."
}
Webhooks signed with HMAC-SHA256; receiver verifies.
OpenAPI specification
A formal OpenAPI 3.1 spec is generated and published at https://registry.locara.app/openapi.json. SDKs can be code-generated.
(open) Whether to publish openapi-generated clients (Rust, TS, Python, Go) or expect users to generate their own.
Implementation notes
- The API runs as Cloudflare Workers + Postgres (Neon). Stateless workers; all state in DB.
- All endpoints are idempotent where appropriate (POST endpoints can include
Idempotency-Keyheader). - All write endpoints require CSRF protection (origin checks for browser callers; not needed for CLI).
- Audit logging on all admin endpoints + publisher account changes.
- Standard 60-day retention on logs (operational); permanent retention for security-relevant events.
Open questions
- (open) GraphQL alternative? Probably not — REST is simpler for our use cases.
- (open) WebSocket for live submission status streaming? Probably yes for CI dashboards; could be SSE.
- (open) Federation with third-party registries — protocol for cross-registry discovery? Defer to v2.
- (open) Rate limit tiers for verified publishers? Probably yes; verified gets higher limits.
- (open) Public API for “list all apps that use model X” / “all apps with capability Y”? Useful for security research; should be public.
Cross-references
- 12-registry.md — registry concept + architecture
- 16-build.md — CI pipeline that calls these endpoints
- 14-trust-safety.md — review pipeline + revocation
- 40-operational-security.md — admin endpoint auth