API documentation

Base URL: https://api-gigrunner.softrace.pt/api/v1
Full endpoint reference + integration guide for the GigRunner / Cue Companion app.

Integration guide (app)

For whoever implements the desktop/mobile client. The website handles registration/purchase; the app handles login, licence validation and AI credit consumption.

1. Setup

Base URL
https://api-gigrunner.softrace.pt/api/v1
Format
JSON · Content-Type: application/json · Accept: application/json
Auth
Authorization: Bearer <token>

During development you can use the Playground to validate responses before wiring the app.

2. Login (once / when expired)

App login screen → POST /login with email and password.

POST /api/v1/login
{ "email": "user@email.com", "password": "…" }

→ 200
{
  "user": { "id", "uuid", "name", "email" },
  "license": { "valid", "status", "plan", "starts_at", "expires_at" },
  "credits": { "ai": 50 },
  "token": "1|…"
}

Persist securely on disk: token, user.uuid, license, credits, and last_validated_at = now.

If license.valid === false: only allow the “activate / buy licence” screen (open the website in the browser). Do not unlock the full app.

3. Next startup (token already stored)

  1. Read token from disk. If missing → login screen.
  2. When online: GET /license (or GET /me if you also want user + credits).
  3. Update local cache (license, credits if present, last_validated_at).
  4. If valid === true → normal app. Otherwise → block / ask to purchase.
  5. If 401 → invalid token → clear cache → login.

The user does not log in again on every launch if the token is still valid.

4. Using AI (required)

Before every AI request to your infra / model:

POST /api/v1/credits/consume
Authorization: Bearer …
{ "type": "ai", "amount": 1, "reason": "cue_suggest" }

→ 200  { "ok": true, "balance": 49, "credits": { "ai": 49 } }
→ 402  { "message": "Insufficient credits.", "credits": { "ai": 0 } }
  • 200 → update local balance → run AI.
  • 402 → do not call AI; show “buy more credits” UI (link to site).
  • Do not rely only on a cached balance to authorise spend — the server decides.
  • Optional: send a unique reference per operation (e.g. job id) for auditing / future double-spend protection.

4b. Audio analysis (Magic Chords)

The app must not call Magic Chords directly. Use GigRunner URLs. The backend picks the provider (today: Magic Chords). Requires a valid licence; consumes 1 AI credit when creating the job.

POST /api/v1/audio/jobs
{ "url": "https://…/song.mp3", "kind": "analyze" }
  or multipart: file + kind

→ 201 { "job": { "id", "status", "progress", … }, "credits" }

GET /api/v1/audio/jobs/{id}           → poll until status=complete
GET /api/v1/audio/jobs/{id}/result    → chords, tempo, key, …

kind: analyze (chords/tempo/key) or transcribe (lyrics). Jobs take time — poll every 2–5s.

5. Offline (proposal)

Rehearsals may have no network. Suggested rule (agree on N):

  • If cache has license.valid === true and now - last_validated_at < N days (e.g. 7) → allow app use.
  • AI features require network (they need /credits/consume). Offline → disable AI or show a clear message.
  • When back online → revalidate immediately (GET /license + optional GET /credits).
  • If online the API says invalid/revoked → block even with an old cache.

6. What the app should do per error

401
Clear token → login screen
402
No AI credits → buy / top-up UI
422
Show validation message (wrong login, etc.)
5xx / network
Soft retry; on startup, apply offline rule

7. Client checklist

  • □ Configurable Base URL (dev / production)
  • □ Login → store token + uuid + license + credits
  • □ Startup with token → GET /license (do not ask for password again)
  • □ Block app if license.valid === false
  • □ Before generic AI → POST /credits/consume; handle 402
  • □ Audio analysis → POST /audio/jobs + poll + /result
  • □ “Register / Buy” link → website (browser)
  • □ Optional logout → POST /logout + clear disk
  • □ Documented offline rule (N days; AI online only)

Test endpoints (activate-test, purchase-test) are for backend/playground — the production app does not need to call them; purchase will be on the website.

Typical flow (summary)

  1. User creates an account on the website.
  2. Buys a licence (e.g. €10) → gets an active app + initial credits (e.g. 50 AI).
  3. In the app: login → token + license + credits.
  4. Before each AI use: POST /credits/consume. If 402 → no balance, ask for top-up.

Authentication

Protected endpoints need the header:
Authorization: Bearer <token>
Accept: application/json

The token comes from POST /login or POST /register. The end user never creates tokens by hand — that is the app’s job.

Endpoints

GET /api/v1/health public
Checks whether the API is online.
Response: { "ok": true, "app": "...", "env": "..." }
POST /api/v1/register public
Creates an account.
Body: name, email, password, password_confirmation
Response 201: { user, license, credits, token }
POST /api/v1/login public
App login.
Body: email, password
Response: { user, license, credits, token }
GET /api/v1/me Bearer
Account data + licence + credits.
Response: { user, license, credits }
GET /api/v1/license Bearer
Licence state (what the app validates on launch).
Response:
{
  "license": {
    "valid": true,
    "status": "active",
    "plan": "trial",
    "starts_at": "2026-09-27T00:00:00+01:00",
    "expires_at": "2026-10-27T00:00:00+01:00"
  }
}
No licence: valid: false, status: "inactive".
POST /api/v1/license/activate-test Bearer · test
Creates a 30-day trial licence (until payments exist).
Response 201: { license }
POST /api/v1/license/revoke Bearer · test
Revokes the current licence (to test app lockout).
Response: { license }

Model: licence vs credits

Licence = may use the app (gate).
Credits = balance per type (currently only ai) for paid actions.

You do not need to “check credits” in a loop. The rule is:

  • On app open → GET /license (and optionally offline cache).
  • Before each AI action → POST /credits/consume (atomic on the server).
  • If 402 → show “no credits” and a link to buy a pack.

There is a ledger (credit_transactions) to audit purchases and consumption.

GET /api/v1/credits Bearer
Balances + available packs.
Response: { credits: { ai }, packs: [...] }
POST /api/v1/credits/consume Bearer
Consumes AI credits.
Body: type (ai), amount? (default 1), reason?
200: { ok, type, balance, credits }
402: insufficient credits
POST /api/v1/credits/purchase-test Bearer · test
Simulates a pack purchase (until Stripe exists).
Body: { "pack": "license_standard" }
Packs: license_standard (€10 → licence + 50 AI), ai_50, ai_200.
POST /api/v1/audio/jobs Bearer
Creates analysis/transcription. Current provider: Magic Chords. Consumes 1 AI credit.
JSON: { "url": "https://…", "kind": "analyze"|"transcribe" }
multipart: file + kind
201: { job, credits } · 402 no credits · 403 no licence
GET /api/v1/audio/jobs/{id} Bearer
Job status (queued / processing / complete / failed) + progress.
GET /api/v1/audio/jobs/{id}/result Bearer
Full provider result (chords, tempo, key, …).
409 if not yet complete.
POST /api/v1/logout Bearer
Revokes the current token.
Response: { "ok": true }

Common errors

401
Missing or invalid token
402
Insufficient credits (consume)
422
Validation (wrong password, invalid pack, etc.)

user object

{
  "id": 1,
  "uuid": "63f50785-…",
  "name": "Name",
  "email": "user@email.com"
}

The uuid is stable — the app can store it locally with the token and licence state.