openapi: 3.0.3 info: title: jsdeck visitor (tenant) auth API description: | Per-app visitor accounts under `/public/apps/{slug}/users/...` on the jsdeck JSON API (`/api/v1`). Sessions use opaque **Bearer** tokens prefixed **`tuat_`**. **Datastore:** Owner-only JSON records use **`visibility`** on **PUT** `/public/data/{slug}/records/{key}` — see [/datastore-api.yaml](/datastore-api.yaml) for core datastore paths and modes (`public_read` / `private`). When both **`store_`** and **`tuat_`** are needed, use **`Authorization: Bearer store_...`** with **`X-Tenant-User-Token: tuat_...`**, or **`Authorization: Bearer tuat_...`** with **`X-App-Key: store_...`**. Public routes: **no CSRF** (intended for SPAs on `*.jsdeck.com`). version: "1.0.0" servers: - url: https://jsdeck.com/api/v1 description: Production - url: http://127.0.0.1:8080/api/v1 description: Local PHP dev server tags: - name: TenantUsers description: Register, login, logout, me, password reset for visitors of a hosted app paths: /public/apps/{slug}/users/register: post: tags: [TenantUsers] summary: Register a visitor for this app parameters: - $ref: '#/components/parameters/Slug' requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: type: string format: email password: type: string minLength: 8 responses: '201': description: Created content: application/json: schema: $ref: '#/components/schemas/RegisterOrLoginResponse' '409': $ref: '#/components/responses/Error' '429': $ref: '#/components/responses/Error' /public/apps/{slug}/users/login: post: tags: [TenantUsers] summary: Log in parameters: - $ref: '#/components/parameters/Slug' requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: type: string password: type: string responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/RegisterOrLoginResponse' '401': $ref: '#/components/responses/Error' '429': $ref: '#/components/responses/Error' /public/apps/{slug}/users/logout: post: tags: [TenantUsers] summary: Revoke current session parameters: - $ref: '#/components/parameters/Slug' security: - tenantBearer: [] responses: '200': description: OK content: application/json: schema: type: object properties: ok: type: boolean '401': $ref: '#/components/responses/Error' /public/apps/{slug}/users/me: get: tags: [TenantUsers] summary: Current visitor parameters: - $ref: '#/components/parameters/Slug' security: - tenantBearer: [] responses: '200': description: OK content: application/json: schema: type: object properties: user: type: object properties: id: type: integer email: type: string '401': $ref: '#/components/responses/Error' /public/apps/{slug}/users/forgot-password: post: tags: [TenantUsers] summary: Request password reset email description: | Always returns **200** with a generic message (no email enumeration). Optional **`redirectPath`** (e.g. `/reset-password`) or **`redirectUrl`** (full `https` URL; host must be `{slug}.jsdeck.com`). Do not send both. parameters: - $ref: '#/components/parameters/Slug' requestBody: required: true content: application/json: schema: type: object required: [email] properties: email: type: string redirectPath: type: string description: Path on hosted app; default `/reset-password` if neither path nor URL set redirectUrl: type: string description: Full https URL; host must match app subdomain responses: '200': description: OK content: application/json: schema: type: object properties: message: type: string devToken: type: string nullable: true description: Present in development only when mail not sent '400': $ref: '#/components/responses/Error' '429': $ref: '#/components/responses/Error' /public/apps/{slug}/users/reset-password: post: tags: [TenantUsers] summary: Set new password with token from email parameters: - $ref: '#/components/parameters/Slug' requestBody: required: true content: application/json: schema: type: object required: [token, newPassword] properties: token: type: string newPassword: type: string minLength: 8 password: type: string description: Alias for newPassword responses: '200': description: OK content: application/json: schema: type: object properties: message: type: string '400': $ref: '#/components/responses/Error' /public/apps/{slug}/users/reset-password/status: get: tags: [TenantUsers] summary: Optional — validate reset token before showing password form parameters: - $ref: '#/components/parameters/Slug' - name: token in: query required: true schema: type: string responses: '200': description: OK content: application/json: schema: oneOf: - type: object required: [valid] properties: valid: type: boolean enum: [true] expiresAt: type: string - type: object required: [valid, reason] properties: valid: type: boolean enum: [false] reason: type: string enum: [invalid, expired] components: parameters: Slug: name: slug in: path required: true schema: type: string securitySchemes: tenantBearer: type: http scheme: bearer bearerFormat: tuat_ schemas: RegisterOrLoginResponse: type: object properties: accessToken: type: string expiresAt: type: string user: type: object properties: id: type: integer email: type: string ErrorBody: type: object properties: error: type: object properties: code: type: string message: type: string responses: Error: description: Error content: application/json: schema: $ref: '#/components/schemas/ErrorBody'