API reference

Everything the MCP tools and the CLI do goes through this REST API — you can drive it directly with curl.

Basics

curl -s https://nomac.app/api/v1/me \
  -H "Authorization: Bearer $NOMAC_API_KEY"

Account

GET/me

Your org, plan tier, how you authenticated, and project/connection counts.

GET/api-keys

List active keys (id, label, last used). The token itself is never shown again.

POST/api-keys

Body { "label"? }. Returns { id, label, token } — the token appears exactly once.

DELETE/api-keys/:id

Revoke a key.

Device login (what nomac login uses; both endpoints are unauthenticated — the code is the secret): POST /device/start returns { device_code, user_code, verification_uri, interval }; the human approves in the browser while the client polls POST /device/token with the device_code until { status: "approved", api_key } — the key is returned exactly once.

Apple connections

POST/connections

Connect an App Store Connect API key. Body { p8, key_id, issuer_id } — validated live against Apple before anything is stored; the .p8 is encrypted at rest.

GET/connections

List connections with status, team name and signing-certificate expiry.

GET/connections/:id

One connection, health-checked.

DELETE/connections/:id

Remove a connection.

POST/connections/:id/webhooks

Body { asc_app_id, base_url? }. Registers NoMac as an ASC webhook receiver for build/version/feedback events, so state updates arrive without polling.

Projects & snapshots

POST/projects

Body { name, connection_id? } — if the org has exactly one Apple connection it is linked automatically.

GET/projects

List projects (id, name, bundle id, scheme, ASC app id).

GET/projects/:id

One project.

DELETE/projects/:id

Soft-delete a project.

POST/projects/:id/snapshots

The body is a gzipped tarball of the working tree (Content-Type: application/gzip) — this is nomac push. Secret-looking files are deny-listed at intake. Auto-detects .xcodeproj, scheme, bundle id and version, resolves the matching ASC app record, and runs source-level review lint. Returns { snapshot_id, commit, files, detected, asc_app_id, lint } plus a warning if no ASC app record exists for the bundle id yet.

GET/projects/:id/feedback

TestFlight return channel: crash reports and screenshot feedback from testers, each with device, OS and comment — structured for an agent to act on.

Builds

POST/builds

Body { project_id, workflow } where workflow is release (signed, uploaded to TestFlight — the default) or smoke (fast unsigned compile check). Builds the latest snapshot; build numbers increment automatically. Every attempt counts toward your monthly cap (Starter 50, Pro 300) — over cap returns 402.

GET/builds?project_id=…

Latest 50 builds, newest first.

GET/builds/:id

Build status. state walks queued → mirrored → dispatched → building → uploading → processing → ready (or failed); a full release run is typically ~3 minutes. Poll this — it also nudges the pipeline forward.

GET/builds/:id/logs

Per-step logs and, for failures, the relevant excerpt.

GET/builds/:id/failure

The structured failure an agent acts on: { stage, code, kind, error_lines, hints }. kind: "project" means fix the source; kind: "account" means a human must act (a deep link is included).

Review & App Store

POST/review-lint

Body { project_id }. The full review-readiness report: source findings from the latest push plus live store findings from ASC, each graded red/yellow/green with evidence and a fix hint. Some findings include ready-to-apply patches.

GET/metadata-schema

The deterministic contract for listing metadata: every field with its char limit, plus screenshot display types and their exact allowed pixel dimensions.

GET/projects/:id/metadata

Current App Store listing state, read live from ASC.

PUT/projects/:id/metadata

Write listing fields. Body { locale?, fields: { name, subtitle, privacy_policy_url, description, keywords, whats_new, support_url, marketing_url, promotional_text } } — all optional, validated against Apple's limits before any ASC write (422 with per-field errors otherwise). Also accepts primary_category, age_rating (human-attested answers), content_rights, copyright, review_contact (with optional demo account) and price (v1: "FREE" only).

POST/projects/:id/screenshots

Body { display_type, locale?, images: [{ filename, data }] } with base64 PNG data. Every image is validated locally for exact dimensions first, then the set for that display type is replaced all-or-nothing — a bad image never leaves a half-updated listing.

POST/publish

Body { project_id, confirm?, force? }. Without confirm: a staged dry-run — preflights a ready build and the lint gate, creates/reuses the draft review submission, and returns Apple's own structured blocker list if anything is missing. With confirm: true: submits for review — irreversible, so agents must ask their human first. force: true overrides a red lint gate (still requires confirm).

GET/submissions/:id

Track a review submission's state after publish.

Support

POST/report-issue

Body { description, ref_id? } — pass a build/project/submission id and the full context is attached automatically. Deduped by error fingerprint, rate-limited to 10/day per org.

GET/report-issue

Your tickets and their replies.

GET/health

Unauthenticated liveness check.

Also see the CLI reference (including the CI/CD guide), /install for agents, and llms.txt.