Yama // API · Mobile

Mobile integration guide

How a mobile app talks to the Yama chat API (v1), and in which order.

Authentication model

The app authenticates as an end-user with a short-lived JWT. The app never holds tenant credentials:

  1. Your product backend authenticates the user (your own login) and calls Yama server-to-server to mint a token for that user.
  2. It returns the JWT to the app.
  3. The app sends it on every Yama call: Authorization: Bearer <jwt>.

The token is valid for 24 h. While it is still valid, the app can extend the session itself with POST /api/v1/token/refresh (no backend round-trip). Once it has fully expired, ask your backend to mint a new one. All resources use UUID ids; rooms can also be addressed by their slug.

Base URL — Production: https://yama.modulotech.fr · Staging: https://staging.yama.modulotech.fr. Always over HTTPS.

Recommended call order

  1. Get a user token — minted by your backend (server-to-server), then handed to the app.

    Your backend authenticates as the tenant (its X-Api-Key + X-Api-Secret — never shipped in the app) and mints a JWT for one of its users, identified by the external_id your backend assigned:

    # server-side only — provision the user once (idempotent on external_id)
    curl -X POST https://yama.modulotech.fr/api/v1/users \
      -H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET" \
      -H "Content-Type: application/json" \
      -d '{"user":{"external_id":"ext-alice","first_name":"Alice"}}'
    
    # server-side only — mint a token for that user (valid 24h)
    curl -X POST https://yama.modulotech.fr/api/v1/users/ext-alice/token \
      -H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET"
    
    # 201 Created
    { "token": "eyJhbGciOiJIUzI1Ni…", "user": { "id": "uuid", "external_id": "ext-alice", "display_name": "Alice" } }

    The app receives token and uses it as the bearer below. It never sees X-Api-Key/X-Api-Secret. Re-mint when the token expires.

    Or — direct password login. If the user has a password (set from the admin), the app can log them in itself, without your backend. The tenant slug is in the URL (users are only unique within a tenant):

    curl -X POST https://yama.modulotech.fr/api/v1/tenants/<tenant-slug>/login \
      -H "Content-Type: application/json" \
      -d '{"login":{"email":"alice@example.com","password":"••••••"}}'
    
    # 201 Created
    { "token": "eyJhbGciOiJIUzI1Ni…", "user": { "id": "uuid", "external_id": "ext-alice", "display_name": "Alice" } }
  2. Verify the token / load the profileGET /api/v1/me
    curl https://yama.modulotech.fr/api/v1/me -H "Authorization: Bearer $JWT"
  3. Mark the user online (on app open) — PATCH /api/v1/me
    curl -X PATCH https://yama.modulotech.fr/api/v1/me \
      -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
      -d '{"status":"connected"}'
  4. List the user's roomsGET /api/v1/rooms?mine=true (omit mine to discover all rooms of the tenant). Each room carries member, and — for rooms the user belongs to — unread_count (the current user's unread messages; null for non-member rooms).
  5. Join a room if neededPOST /api/v1/rooms/{slug}/membership (idempotent)
  6. Load a room's messagesGET /api/v1/rooms/{slug}/messages Filters: ?unread=true, ?from= / ?to= (ISO8601) for incremental sync. Each message has reads_count, read_by_me, and a nested user object (the author: id, external_id, display_name, first_name, last_name, status) alongside the raw user_id.
    curl "https://yama.modulotech.fr/api/v1/rooms/support/messages?from=2026-06-18T00:00:00Z" \
      -H "Authorization: Bearer $JWT"
  7. Send a messagePOST /api/v1/rooms/{slug}/messages
    # text
    curl -X POST https://yama.modulotech.fr/api/v1/rooms/support/messages \
      -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
      -d '{"message":{"content":"Hello 👋"}}'
    
    # with a file (multipart)
    curl -X POST https://yama.modulotech.fr/api/v1/rooms/support/messages \
      -H "Authorization: Bearer $JWT" \
      -F "message[content]=See attached" -F "message[file]=@/path/photo.jpg"

    Attachments: photos, videos or PDF only (incl. Apple HEIC/HEIF and QuickTime/MOV), max 75 MB. Anything else returns 422.

  8. Mark messages read (e.g. the latest visible one) — POST /api/v1/messages/{id}/read This also marks every earlier message in the room as read (so one call per scroll is enough). Response includes marked_count. Undo a single one with DELETE /api/v1/messages/{id}/read.
  9. Open a single message (with read receipts) — GET /api/v1/messages/{id} Returns the same fields as in the list (incl. the author user object), plus reads: who read it and when, and file if any.
  10. Leave a room (optional) — DELETE /api/v1/rooms/{slug}/membership
  11. Mark the user offline (on background) — PATCH /api/v1/me with {"status":"disconnected"}.
  12. Log out (on sign-out) — POST /api/v1/logout. Revokes every token issued so far for that user (so a stolen token stops working) and marks them disconnected. After this, the app must obtain a fresh token (step 1) to use the API again.

Endpoint reference

MethodPathPurpose
POST/api/v1/tenants/{tenant_slug}/loginDirect login (email + password) → JWT
GET/api/v1/meCurrent user profile
PATCH/api/v1/meUpdate status (connected/disconnected/unknown)
GET/api/v1/roomsList rooms (?mine=true)
GET/api/v1/rooms/{slug}Room detail (slug or id)
POST/api/v1/rooms/{slug}/membershipJoin
DELETE/api/v1/rooms/{slug}/membershipLeave
GET/api/v1/rooms/{slug}/messagesList messages (unread, from, to)
POST/api/v1/rooms/{slug}/messagesSend a message (text and/or file)
GET/api/v1/messages/{id}Message detail + read receipts
POST/api/v1/messages/{id}/readMark read
DELETE/api/v1/messages/{id}/readMark unread
POST/api/v1/token/refreshExchange a valid JWT for a fresh one
POST/api/v1/logoutRevoke current tokens + mark disconnected

Errors to handle

StatusMeaningWhat the app should do
401Missing / invalid / expired tokenRequest a fresh JWT from your backend, retry once
403Not a member of the roomJoin the room first, or hide the action
404Unknown room / messageRefresh the list; the resource may be gone
422Invalid payload (e.g. empty message, bad status)Show a validation error; check errors