Skip to main content
Version: Sprint 4

API Reference

Authentication and Access​

GET and POST /api/auth/* delegate to Better Auth for social sign-in, callbacks, sessions, and sign-out. GitHub and Google are configured. Do not infer an enabled email/password registration flow from generic Better Auth endpoints.

Protected routes validate a session and normally return 401 when absent. Owner/author/admin checks vary: consult the table and route-specific errors. Admin scheduling requires "user".role = 'admin'.

Documents, Users, and Invitations​

MethodPathAccessContract
GET/api/filesSigned-in ownerOwn files with latest version metadata, newest update first
POST/api/filesSigned inCreate file/version 1 from multipart file; optional problemSlug
GET/api/files/sharedSigned inFiles where caller is a contributor, with owner ID
GET/api/files/:fileIdAny signed-in userLatest bytes for shared-link reading
PUT/api/files/:fileIdOwnerNew multipart file version, keeping latest four
PATCH/api/files/:fileIdOwnerRename with { "filename": "..." }
DELETE/api/files/:fileIdOwnerDelete file/version bytes and metadata; 204 on success
GET/api/files/:fileId/versionsOwnerVersions newest first
GET/api/files/:fileId/versions/:versionIdOwnerSpecific stored version bytes
GET/api/users/search?q=...Signed inEmail partial search; at least two characters, at most ten results
GET/api/users/recentSigned inUp to eight recent collaborators on the caller's files
GET/api/files/:fileId/contributors/checkSigned in{ isOwner, isContributor, canCollab }
GET/api/files/:fileId/contributorsOwner or contributorCurrent contributor list
POST/api/files/:fileId/contributorsOwnerDirect contributor insertion with { "userId": "..." }
DELETE/api/files/:fileId/contributorsOwnerRemove { "userId": "..." }
GET/api/files/:fileId/invitesOwnerPending invites with invitee name/email
POST/api/files/:fileId/invitesOwnerSend pending invite with { "userId": "..." }; 201 or duplicate 409
GET/api/invitesSigned-in inviteeCaller’s pending invitations
POST/api/invites/:inviteIdPending invitee{ "action": "accept" } or "decline"
DELETE/api/invites/:inviteIdFile ownerCancel pending invite; 204

File Constraints and Responses​

Uploads require a nonempty File part; the limit is 100 * 1024 * 1024 bytes (100 MiB). Invalid/empty files return 400; oversized files return 413. Creation returns 201 with file/version metadata and an explicit file_id. Saving a version returns its row with 200. Rename validates a nonempty filename of at most 255 characters.

Downloads return bytes with Content-Type, Content-Disposition, and X-Version-Number. The latest-version route also returns X-Owned and optional X-Problem-Slug so a saved solution can reopen its question.

File/version inserts use database transactions after the byte upload. Pruned storage deletion is best-effort after commit. Not every endpoint or external storage operation is part of a single distributed transaction.

Accepting an invite changes its status and grants contributor access atomically. Pending invites are not contributor access. The direct contributor API still exists separately from the invitation acceptance workflow.

Problem Catalog and Grading​

MethodPathAccessContract
GET/api/problemsPublicCatalog with caller's solved/attempt status; anonymous status defaults false/zero
GET/api/problems?mine=1Signed inCaller-authored and built-in catalog entries
POST/api/problemsSigned inPublish a definition; 201, invalid 400, duplicate slug 409
GET/api/problems/:slugPublicProjected definition; non-authors receive only public cases
DELETE/api/problems/:slugAuthor, non-built-inRemove problem; built-ins return 403, success 204
POST/api/problems/:slug/submitPublicGrade { "machine": { ... } } against server cases

Publishing accepts slug, title, difficulty, statement, variant, tapeCount, starterSource, and cases. A case includes name, input, expectedOutput, expected, cutoff, and visibility. Validation requires a 2-63 character lowercase/digit/dash slug, 1-20 cases including a public case, and one of the six supported machine variants. Tape count is 1-8. Case input/output bounds are 64/128 characters and cutoff is at most 10,000.

Submission validation bounds machine size before execution. Grading returns:

{
"accepted": false,
"passed": 1,
"total": 2,
"cases": [
{ "label": "example", "name": "example", "hidden": false, "passed": true, "reason": "ok", "steps": 3 },
{ "label": "hidden-1", "name": "hidden-1", "hidden": true, "passed": false, "reason": "mismatch", "steps": 4 }
],
"notes": [],
"recorded": false,
"attempts": null
}

This is an illustrative shape, not a captured run. Public case verdicts can include detail; hidden ones omit expected/actual tape detail. Anonymous work is graded but not recorded. Signed-in work upserts account progress and increments attempts. There is no general-purpose /api/tm/parse or /api/tm/run route in the inspected tree.

Daily Play and Archive​

Daily routes use the HttpOnly fla_daily player cookie; sign-in is optional. The server date uses DAILY_TIMEZONE (default Africa/Johannesburg).

MethodPathContract
GET/api/daily/todayPuzzle public view, solved state, probes/answers left, attempts, streak, secondsUntilRollover
POST/api/daily/probe{ "input": "..." }; run a black-box reference, spend a probe, return tape/result/steps and budgets
POST/api/daily/answerBlack-box { "predictions": [{ "tape": "...", "result": "acc" }] } or build { "machine": { ... } }
GET/api/daily/historyCaller’s past puzzle progress/history
GET/api/daily/archivePast scheduled puzzle archive
GET/api/daily/archive/:datePast puzzle public view and reveal-policy-controlled reference data

Unscheduled today returns a 200 object with puzzle: null and a countdown; probe/answer instead return 404. Probing a build daily returns 405. Reserved prediction inputs and symbols outside the hidden machine's alphabet are rejected before spending a probe. The endpoint reports only the offending typed symbol, not the full hidden alphabet.

Answers are validated before budget spending. Already solved returns 409; exhausted budgets return 429 with rollover information. Black-box answers return feedback tiles; build answers use the same authoritative grader as catalog problems. Final lockout can include a reference reveal for that player. Current/future archive dates are rejected.

lockout, archive_only, and never govern reference-machine disclosure. Archive build views also project cases according to the source: generated past build cases can become visible, while catalog-backed builds still use the catalog non-author projection. Do not treat a past-date view as permission to return all catalog private cases.

Daily Administration​

MethodPathAdmin-only behavior
GET/api/admin/dailyCalendar rows/statistics and server's today
POST/api/admin/dailySchedule/upsert a draft date; 201
POST/api/admin/daily/previewCompile, generate observations/starter, self-grade, return errors/warnings and transition reachability; no scheduling write
PUT/api/admin/daily/:idRewrite/move a scheduled puzzle; date conflict 409
DELETE/api/admin/daily/:idDelete schedule/progress under configured cascades; 204

Draft fields are date, kind, statement, probeBudget, answerBudget, revealPolicy, problemSlug, machineSource, inputs, privateInputs, and hiddenRefs. Black-box mode requires a reference and 1-8 prediction inputs. Build mode takes a reference-generated starter or catalog source and has zero probe budget. Answer budget is 1-10; black-box probe budget is 0-20.

Preview reports reached/hidden transitions and generated case/slot counts. Scheduling/replacing an existing date retains earlier attempts; the admin must understand that they still describe the old content.

Groq AI Proxy​

POST /api/ai/chat​

The current handler does not require a Better Auth session. It uses optional x-groq-key from the request, otherwise GROQ_API_KEY from the server. A missing key returns 500 with setup guidance.

{
"messages": [{ "role": "user", "content": "Explain this transition" }],
"editorContent": "setVariant(single-tape); ...",
"smartModel": true
}

The server calls https://api.groq.com/openai/v1/chat/completions with a system prompt generated from current editor content and the last three supplied messages. smartModel selects qwen/qwen3.8-27b (4096-token output cap) or openai/gpt-oss-20b (2048-token cap). These are source-configured identifiers, not a guarantee of provider availability. Temperature is 0.7.

Success returns { "content": "...", "rateLimited": false }. Invalid JSON or empty messages return 400; provider limits return 429; provider/network errors return 502. The server keeps an in-process estimated credit window (8000 credits per 60 seconds per key), can wait before sending, and accounts for returned token usage. This is not a persistent/distributed quota.

Data boundary: messages and editor source are sent to Groq. The browser AI panel stores an optional user key in local storage and sends it to this route. Do not put key values into documentation or VITE_ build variables, and do not paste sensitive machine/source content into the assistant without considering the external service's data handling. No Groq SDK is installed; the integration uses fetch, the project prompt builder, and Markdown/code rendering.

External File Storage Contract​

The application invokes GET, POST, and DELETE <FILE_SERVER_URL>/internal/:ownerUserId/:storagePath using X-Internal-Key. Uploads send raw bytes; downloads stream bytes. The storage server source is not in the inspected application tree, so its implementation, port, health route, filesystem layout, and path-safety guarantees are not verified here.