openapi: 3.0.3
info:
  title: naturali.ai — Auth API
  version: 1.0.0
  description: >
    The human-session surface. Interactive clients (console, native apps) sign in
    with a code emailed to the address — the only human credential — and receive
    a short-lived access JWT plus a rotating refresh token; programmatic callers
    use API tokens instead (see the API Keys API). Both resolve to the same
    project + capability authorization below the edge. Built on the ttoss
    auth-core primitives (signJwt, createRefreshRotation, generateOneTimeToken).
    See API.md §1 (Authentication).
  contact:
    name: naturali.ai
    url: https://naturali.ai
servers:
  - url: '{baseUrl}'
    description: Host of your naturali.ai deployment; every path carries the /v1 prefix.
    variables:
      baseUrl:
        description: Base host URL.
        default: https://api.naturali.ai
tags:
  - name: Auth
    description: Sign in, refresh and end interactive human sessions.
security:
  - bearerAuth: []
paths:
  /v1/auth/code:
    post:
      tags: [Auth]
      summary: Email a sign-in code
      description: >
        Emails a short numeric code to the address. Always responds 200 with the
        same body whether or not the address has an account, so it never leaks
        existence. A first-time address gets an account on its first successful
        verification, so this is both sign-up and log-in. Issuing a code
        invalidates any previous one for the same address.
      operationId: requestSignInCode
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignInCodeRequest'
      responses:
        '200':
          description: If the address can receive a code, one has been sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Acknowledgement'
        '400':
          $ref: '#/components/responses/BadRequest'
  /v1/auth/code/verify:
    post:
      tags: [Auth]
      summary: Redeem a sign-in code
      description: >
        Exchanges an emailed code for a session, creating the account if the
        address is new. The code is single-use and short-lived. A code is
        destroyed after too many wrong guesses, since six digits is small enough
        to guess given unlimited attempts — the client must then request a new
        one rather than retry.
      operationId: verifySignInCode
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignInCodeVerify'
      responses:
        '200':
          description: Authenticated; a session is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSession'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: >
            The code is wrong, expired, or already used (`invalid_token` /
            `expired_token`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: >
            Too many incorrect attempts (`too_many_attempts`); the code has been
            destroyed and a new one must be requested.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/auth/refresh:
    post:
      tags: [Auth]
      summary: Refresh a session
      description: >
        Exchanges a valid refresh token for a new access JWT and a rotated
        refresh token, taken from the body or from the `refresh_token` cookie set
        at sign-in — a browser sends an empty body and the cookie carries the
        credential. Refresh tokens are single-use; presenting a
        previously-rotated token is treated as reuse and revokes the whole
        session family (createRefreshRotation reuse detection), except within a
        few seconds of the rotation, where it is treated as two tabs racing on
        one cookie and rotated again.
      operationId: refreshSession
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshRequest'
      responses:
        '200':
          description: A new session (access + rotated refresh).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSession'
        '401':
          description: The refresh token is invalid, expired or was reused.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/auth/logout:
    post:
      tags: [Auth]
      summary: Log out
      description: >
        Revokes the current refresh token (and its rotation family) and clears
        the `refresh_token` cookie. Pass `all: true` to revoke every active
        session for the user.
      operationId: logout
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LogoutRequest'
      responses:
        '204':
          description: Session(s) revoked.
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/auth/me:
    get:
      tags: [Auth]
      summary: Get the current identity
      description: Returns the user behind the presented access token.
      operationId: getCurrentUser
      responses:
        '200':
          description: The authenticated user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A session access JWT (or a naturali API key, nat_sk_…).
  responses:
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequest:
      description: The request was malformed or failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    SignInCodeRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
          example: ana@acme.com
    SignInCodeVerify:
      type: object
      required: [email, code]
      properties:
        email:
          type: string
          format: email
          example: ana@acme.com
        code:
          type: string
          description: The code from the email, as sent — digits only.
          pattern: '^[0-9]{4,12}$'
          example: '481902'
    Acknowledgement:
      type: object
      description: >
        A deliberately uninformative confirmation. The wording is identical for a
        known and an unknown address so the response cannot be used to enumerate
        accounts.
      properties:
        message:
          type: string
          example: If the address is registered, an email has been sent.
    AuthSession:
      type: object
      description: >
        A short-lived access JWT plus a rotating refresh token. The refresh token
        is also set as an httpOnly, SameSite=Lax cookie scoped to `/v1/auth`
        (Secure whenever the connection is https), which is what carries a
        browser session across a reload; the body copy is for clients with no
        cookie jar, such as the CLI.
      properties:
        token_type:
          type: string
          enum: [Bearer]
          example: Bearer
        access_token:
          type: string
          description: 'Short-lived access JWT. Send as `Authorization: Bearer <token>`.'
          example: eyJhbGciOiJIUzI1NiJ9...
        expires_in:
          type: integer
          description: Access-token lifetime in seconds.
          example: 900
        refresh_token:
          type: string
          description: Single-use refresh token, rotated on every refresh.
          example: rt_9f2StGXR8Z5jdHi6B...
        user:
          $ref: '#/components/schemas/User'
      required: [token_type, access_token, expires_in, user]
    RefreshRequest:
      type: object
      description: >
        Send an empty object from a browser — the httpOnly `refresh_token` cookie
        is presented instead, and takes over when the body omits the field.
      properties:
        refresh_token:
          type: string
          example: rt_9f2StGXR8Z5jdHi6B...
    LogoutRequest:
      type: object
      properties:
        refresh_token:
          type: string
          description: The refresh token to revoke; defaults to the current session's.
          example: rt_9f2StGXR8Z5jdHi6B...
        all:
          type: boolean
          default: false
          description: Revoke every active session for the user.
    User:
      type: object
      properties:
        id:
          type: string
          description: Public user ID (user_ prefix).
          example: user_V1StGXR8Z5jdHi6B
        email:
          type: string
          format: email
          example: ana@acme.com
        name:
          type: string
          nullable: true
          example: Ana Silva
        email_verified:
          type: boolean
          example: true
        created_at:
          type: string
          format: date-time
          example: '2026-07-17T00:00:00.000Z'
      required: [id, email, email_verified, created_at]
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              example: invalid_token
            message:
              type: string
              example: The code is invalid or expired.
            details:
              type: object
              additionalProperties: true
