DOCS · REST API

API reference

Base URL: https://tilldev.dev (production) or http://localhost:8455 (local). Auth via httpOnly cookies (tp_access JWT, tp_refresh) or Bearer API key.

01Auth

Authentication

Every request requires a valid session — either via cookies set by POST /api/auth/login, or via a Bearer token from a personal API key.

bash
# Cookie session (web app default)
curl https://tilldev.dev/api/projects \
  --cookie 'tp_access=<jwt>; tp_refresh=<token>'

# API key (CLI / CI)
curl https://tilldev.dev/api/projects \
  -H 'Authorization: Bearer tp_9f3a...'
02Endpoints

Auth endpoints

POST /api/auth/signup — create user + org.

text
{ "email": "ada@example.com", "password": "min8chars", "name": "Ada Okafor", "org": "example" }
→ { "ok": true, "org": "example" }

POST /api/auth/login — email + password (+ TOTP).

text
{ "email": "...", "password": "...", "totpToken": "123456" }
→ { "ok": true, "org": "example", "role": "owner" }
→ { "totp_required": true }   // if 2FA enabled

POST /api/auth/logout — revoke refresh, clear cookies.

POST /api/auth/refresh — rotate refresh, mint new access.

POST /api/auth/forgot-password — body { email }; always returns 200 (no enumeration).

POST /api/auth/reset-password — body { token, password }.

POST /api/auth/change-password — body { currentPassword, newPassword }.

POST /api/auth/accept-invite — body { token, password }.

POST /api/auth/sso/start — form org=<slug>; redirects to the workspace’s identity provider. Add link=1, from a signed-in page on this site, to connect your own account instead.

GET /api/auth/sso/callback — the provider redirects here. Signs in only an account connected to that provider’s subject; otherwise emails the account holder a link to connect it.

GET / PUT /api/auth/sso/config — the workspace’s provider. Owner or admin, signed in (not an API key). Endpoints must be https and public; a new issuer disconnects everyone.

GET / POST / PATCH / DELETE /api/auth/sso/domains — domains proven by a TXT record at _tilldev.<domain>; company sign-in creates accounts only on these.

GET / DELETE /api/auth/sso/identities — your own connected company sign-ins.

032FA

Two-factor (TOTP)

POST /api/auth/2fa/setup → { otpauthUrl, backupCodes }

POST /api/auth/2fa/verify — body { token }

POST /api/auth/2fa/disable — body { password }

04Projects

Projects

GET /api/projects — list projects in org.

POST /api/projects — body { name, platform, region } (region: af-south-1 | eu-central-1 | us-east-1; platform: react-native | flutter | ios | android | web).

GET / PATCH /api/projects/:projectId/settings

POST /api/projects/:projectId/rotate-dsn — the old DSN stops accepting events within ~5 minutes.

GET /api/projects/:projectId/issues — project-scoped issue list.

POST /api/projects/:projectId/source-maps — multipart/form-data file + release + filename + symbolication_type (js_source_map | proguard).

GET /api/projects/:projectId/source-maps — list uploads.

DELETE /api/projects/:projectId/source-maps/:mapId

GET /api/projects/:projectId/dsyms — list dSYMs, one row per build UUID.

POST /api/projects/:projectId/dsyms — multipart/form-data file (the DWARF file inside a .dSYM, up to 32 MB) + release + optional dist. Answers with the UUIDs found and any warnings.

POST /api/projects/:projectId/dsyms/uploads — start an upload of a larger file (up to 1 GB): JSON release, filename, size, optional dist. Answers upload_id, part_size and parts.

PUT /api/projects/:projectId/dsyms/uploads/:uploadId/parts/:n — raw bytes of part n (1-based, part_size bytes; the last is the rest). Answers its etag.

POST /api/projects/:projectId/dsyms/uploads/:uploadId/complete — JSON parts: every { part_number, etag }. Registers the file like a direct upload; repeating it is safe.

DELETE /api/projects/:projectId/dsyms/uploads/:uploadId — abandon an upload.

GET / POST /api/projects/:projectId/integrations/github — repo URL + webhook secret (regenerate on POST).

GET / POST / DELETE /api/projects/:projectId/integrations/tracker — Linear or Jira credentials (encrypted at rest).

05Issues

Issues

GET /api/issues — query: status, severity, projectId, limit, offset, sort.

GET /api/issues/:issueId — full detail (breadcrumbs, frames, AI analysis).

PATCH /api/issues/:issueId — body { status } (unresolved | resolved | ignored | regressed | escalated) or { assignee_id }.

DELETE /api/issues/:issueId

GET / POST /api/issues/:issueId/comments

DELETE /api/issues/:issueId/comments/:commentId (author or admin).

POST /api/issues/:issueId/tracker-ticket → creates Linear / Jira issue, links it.

GET /api/issues/search?q=...&projectId=... — full-text search across your issues.

POST /api/issues/nlq — body { project_id, query } → your question is translated to a safe, read-only query over your telemetry (only read-only queries are ever run; project filter injected server-side).

06Alerts

Alerts

GET / POST /api/alerts?projectId=...

json
{
  "projectId": "uuid",
  "name": "High severity spike",
  "trigger_type": "issue_spike",
  "conditions": { "threshold": 50, "window_minutes": 15, "severity": "high" },
  "actions": [{ "type": "slack", "webhook_url": "https://hooks.slack.com/..." }]
}

Trigger types: new_issue, issue_regression, issue_spike, security_event, pre_crash_state, battery_drain_threshold, performance_threshold, critical_path, secrets_anomaly (a TillSecrets agent spending unlike itself; conditions min_severity and agents), ark_signal (a TillArk signal; conditions kinds, sources and min_severity). Updates are checked the same way as creates.

Action types: slack, webhook, email, pagerduty, msteams.

PATCH / DELETE /api/alerts/:ruleId

POST /api/alerts/:alertId/ack — acknowledge a fired alert.

GET /api/alerts/recent?limit=8 — used by the dashboard’s notification bell.

07Metrics

Metrics & performance

GET /api/metrics?projectId=&range=1h|24h|7d|30d&metric=crash_rate|anr_rate|event_count|user_count

GET /api/performance?projectId=&range=7d — P50/P75/P95 cold-start, ANR rate, screen-render times.

08Team

Team

GET /api/team — list members.

POST /api/team — body { email, role }; sends invite email.

PATCH /api/team/:memberId — body { role }.

DELETE /api/team/:memberId

POST /api/invites — bulk invite. Body { emails: [...], role }.

09API keys

API keys

GET /api/api-keys — list (no raw key — only prefix).

POST /api/api-keys — body { name, scopes }. Raw key returned once.

DELETE /api/api-keys/:keyId — immediate revoke.

10AI

Ask Pulse (AI)

POST /api/ask — body { messages: [{ role, content }] }; returns Server-Sent Events.

text
data: {"delta":"Computed from your telemetry..."}
data: {"delta":"```sql\nSELECT ...```"}
data: [DONE]

The model never executes queries. The dashboard renders the highlighted read-only query with a sparkline; results are computed from your telemetry.

11Audit

Audit log

GET /api/audit-log?limit=50&offset=0&action=user.password_changed — admin/owner only.

12Onboarding

Onboarding

GET /api/onboarding/status → { steps: { project_created, sdk_installed, first_event, team_invited }, completed }

13Releases

Releases

GET / POST /api/releases?projectId=...

14Notifications

Notifications

POST /api/notifications/devices — register Expo / FCM push token. Body { token, platform }.

15Webhooks

Webhooks (incoming)

POST /api/integrations/slack/interactive — Slack button payloads. Slack requests are signature-verified.

POST /api/integrations/github/webhook?project=... — GitHub requests are signature-verified against your per-project secret.

POST /api/integrations/tracker/webhook?provider=linear|jira — tracker webhooks are verified per provider.

16Approvals

Actions that need a person

With approvals on (the default), revealing a secret, running a command with secrets, allowing a destination, consenting to a grant, presigning a call, minting a service or agent token, syncing out, lowering a tier and turning approvals off answer 428 with code: STEP_UP_REQUIRED and an approval object. Nothing is done. The person approves at approval.url with a passkey or a confirmed authenticator code; then repeat the same request with x-tilldev-approval: <approval.id>.<approval.claim>. An API key gets 403: it cannot approve.

GET /api/step-up/:id — poll until status is approved, denied or expired.

GET /api/step-up — your requests from the last 30 days (?status=pending for waiting ones).

POST /api/step-up/:id/deny — refuse a request, or stop one allowed to repeat.

GET | PUT /api/step-up/policy — { step_up: "on" | "off" }; owner or admin, and off needs an approval.

The full contract is in /openapi.json under the Approvals tag; the flow is explained in TillSecrets approvals.

17Errors

Error envelope

json
{ "error": "Human-readable message" }
StatusMeaning
400Validation error — check error
401Not authenticated — redirect to /auth/login
403Authenticated but insufficient role
404Resource not found
409Conflict (duplicate slug, already invited, etc.)
423The workspace's customer key refused, so the data this call needs is offline (code: KEY_LOCKED)
428Needs a person's approval — see Approvals
429Too Many Requests — you exceeded your rate limit
500Internal server error
18Beyond this API

Product APIs on their own hosts

Two products serve their own end-user API on a dedicated host, with their own credential and their own OpenAPI document. They are documented here so you know where to look — the reference above covers the cross-product dashboard API only.

APICredentialSpecification
TillAuth — end-user identity (sign-in, sessions, MFA, OIDC)publishable + secret app keysauth.tilldev.dev/openapi.json
TillNotary — transparency log (submit, proofs, checkpoints)tnot_… bearernotary.tilldev.dev/v1/openapi.json

TillNotary reads on a public log need no credential at all — the log is public by construction, so proofs and checkpoints are fetchable by anyone who wants to verify them. See the TillNotary docs for the full surface, and note that its error envelope is the richer { error, code, request_id } form rather than the{ error } shape above.