PraxaLing / docs /API.md
Reubencf's picture
Fix inference auth and app branding
f4d870a

API contract

All routes are served by the Next.js app. Auth is either:

  • Cookie: ll_session=<jwt> (web) — set by the OAuth callback.
  • Authorization: Bearer <jwt> (mobile) — JWT delivered via the langlearn://auth/callback?token=… redirect.

The JWT is signed HS256 with AUTH_SECRET and contains { userId, hfUsername }. TTL is 7 days.

Auth

GET /api/auth/login

Starts the HuggingFace OAuth dance.

Query params:

  • client=mobile — if set, the callback will 302 to langlearn://auth/callback?token=… instead of setting a cookie. Otherwise, cookie + 302 to /practice.

GET /api/auth/callback/huggingface

Handles HF's redirect back. Validates state + verifier cookies, exchanges code, upserts user, mints JWT. Never call directly.

POST /api/auth/logout

Clears the session cookie. For mobile, the client just drops the stored JWT.

Profile

GET /api/me

{
  "user": { "id": "uuid", "hfUsername": "alice", "email": "a@x.y", "avatarUrl": "…" },
  "profile": { "nativeLang": "en", "targetLang": "es", "level": "A2" } | null
}

PUT /api/me/profile

Body:

{ "nativeLang": "en", "targetLang": "es", "level": "A2" }

Responses: 200 with { profile }, 400 on validation errors. Mobile bearer-token requests also receive { token } with a refreshed JWT carrying the updated profile.

Stories

GET /api/stories

Lists stories matching the user's profile (targetLang + level). Response:

{ "stories": [{ "id", "title", "targetLang", "level", "createdAt" }], "profile": {...} }

POST /api/stories

Generates a new story for the user's profile. Body (optional): { "topic": "a walk in the park" }. Response: { "story": { id, title, content, vocab, ... } }.

GET /api/stories/:id

{ "story": { ... full story ... } }

POST /api/stories/:id/read

Marks the story as read by the current user.

Translate

POST /api/translate

{ "text": "perro", "from": "es", "to": "en" }

Response: { "translation": "dog", "cached": true? }.

Vision

POST /api/vision/analyze

Multipart form with field image (≤ 8 MB). Response:

{
  "id": "uuid",
  "caption": "a cat sitting on a couch",
  "objects": [
    { "label": "cat", "translation": "gato", "box": [x1,y1,x2,y2], "score": 0.92 }
  ],
  "sentences": [
    { "target": "El gato está sobre el sofá.", "gloss": "The cat is on the sofa." }
  ],
  "imageUrl": "https://…"  // empty if blob storage isn't configured
}

Error shape

All failures return { "error": "code", "message"?: "…" } with an appropriate HTTP status. Common codes: unauthenticated (401), invalid_input (400), no_profile (400), inference_failed (502), image_too_large (413).