openapi: 3.0.3
info:
  title: naturali.ai — Users API
  version: 1.0.0
  description: >
    The account behind a credential. The Auth API mints and rotates credentials;
    this one reads and edits the user they resolve to, whether that credential is
    an app session JWT or a naturali API key (nat_sk_…).

    `/me` is the only subject this module addresses: nothing in the product gives
    one tenant the right to read another's account, so there is no
    `GET /v1/users/{user_id}` and no directory a tenant can reach. A colleague's
    email surfaces only through the membership listing of a project you are
    already in (`GET /v1/projects/{project_id}/members`).

    Reading or changing *another* account needs the `admin` platform role and an
    operator route, which this contract does not describe.

    The email address is deliberately not editable here. It *is* the credential —
    a code sent to it is how an account is created and signed into — so changing
    it is an authentication flow that must prove control of the new address, not
    a profile edit.
  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: Users
    description: Read and edit the authenticated account.
security:
  - bearerAuth: []
  - oauth2:
      - mcp:access
paths:
  /v1/users/me:
    get:
      tags: [Users]
      summary: Get the current user
      description: Returns the account the presented credential resolves to.
      operationId: getCurrentUser
      responses:
        '200':
          description: The authenticated user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'
    patch:
      tags: [Users]
      summary: Update the current user
      description: >
        Edits the account's display name and whether it receives usage alerts.
        At least one field is required, so a request that misspelled a field is
        rejected rather than answered with a silent 200. Send `name: null` to
        clear the name.


        With `usage_alerts` on, the account is emailed once each time its model
        credit, its runs this month or its indexed storage reaches 75%, 90% and
        100%. Credit is measured as the share spent this month of what the month
        had available, so a top-up lowers it. Usage falling back below a level
        re-arms that level. Figures are read every 15 minutes, so an alert can
        arrive up to that long after the crossing.
      operationId: updateCurrentUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdate'
      responses:
        '200':
          description: The updated user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/users/me/billing:
    get:
      tags: [Users]
      summary: Get the current account's billing standing
      description: >
        The plan the account is on, how long its projects may keep content, how
        much indexed storage it is holding, and the credit it has left — the
        figures the platform already enforces against, readable by the account
        they are enforced against.


        A managed-model generation is refused with `402 insufficient_credit`
        while `credit_balance_usd` is negative, and a feature or a resource count
        outside the plan is refused with `403`, as is a retention window wider
        than `retention_days` or an ingest past `storage_limit_gb`. Every refusal
        names what is missing; this is where the numbers behind them are read.


        Every figure here is a local read, which is what keeps this route cheap
        enough to poll — unlike `GET /v1/users/me/usage`, which asks the meter
        once per project.


        Answers for the caller's own account only, and always the account the
        credential resolves to — an API key answers for the user that minted it.
        The plan gating a *project* is that project's billing owner's, so a
        member of someone else's project reads their own rung here, not that
        project's.
      operationId: getCurrentUserBilling
      responses:
        '200':
          description: The account's plan and credit standing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserBilling'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/users/me/usage:
    get:
      tags: [Users]
      summary: Get the current account's runs this billing cycle
      description: >
        How many runs the account has made this billing cycle, and how many its
        plan includes. A **run is one agent generation or one tool call** — the
        unit every rung is priced in. Every tool call counts, including those an
        agent makes inside a generation; a `client` tool counts nothing.


        Counted across the projects this account is the billing owner of, over
        the current UTC calendar month, from the meter itself. Archived projects
        are included: the runs they already made were made.


        **On the `free` plan the allowance is enforced.** An account that has
        used it is refused `403 plan_limit_reached` with `resource: "runs"` on
        everything that starts a generation and on a direct tool call — on its
        own provider credential as
        much as on a managed model, because a run is a run — until the billing
        month turns or it upgrades. On `pro` and `business` nothing is refused:
        runs past the allowance are billed at the overage rate those plans are
        sold with. A contract plan is not counted.


        The other thing that stops a managed-model generation is a negative
        credit balance, which is `GET /v1/users/me/billing`, and it never
        applies to your own credential.


        **This route counts live; the refusal reads a figure counted every few
        minutes.** So the two can differ by a burst: this is the more current
        number, and a refusal quotes the one it actually enforced.


        It is also more expensive than the balance read: it asks the meter once
        per project. Poll it on the order of minutes, not seconds.
      operationId: getCurrentUserUsage
      responses:
        '200':
          description: The account's runs for the cycle.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserUsage'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/users/me/stops:
    get:
      tags: [Users]
      summary: List what a billing ceiling stopped
      description: >
        Schedule triggers, eval runs, orchestration runs and workflow tasks the
        platform stopped on this account's projects because the credit balance
        went below zero or a Free plan's runs ran out, and that have not been
        restored or dismissed yet. Newest first.


        `blocked_by` names the ceiling still closed: while it is set, nothing
        can be restored. Top up for `debt`; upgrade or wait for the month to turn
        for `run_allowance`. A `marketplace_fee` stop, for work naming a priced
        installed listing, is restored only while the paid balance is positive.
        A project-confined key sees its own project's stops only.
      operationId: listCurrentUserStops
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum items per page — an integer from 1 to 100 (default 20).
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          required: false
          description: The `next_cursor` of the previous page.
          schema:
            type: string
      responses:
        '200':
          description: A page of stops still listed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SpendStopPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/users/me/stops/{stop_id}/restore:
    post:
      tags: [Users]
      summary: Restore a stopped item
      description: >
        Turns one stopped item back on — re-enables the schedule trigger, or
        resumes the orchestration run or workflow task — and stops listing it.
        An item that is no longer stopped (resumed already, finished or deleted)
        is marked restored too.


        Refused with `409 stop_ceiling_closed` while a ceiling is still closed,
        naming it in `details.blocked_by`, and with `409 stop_not_restorable` for
        a cancelled eval run, which can only be dismissed.
      operationId: restoreCurrentUserStop
      parameters:
        - $ref: '#/components/parameters/stop_id'
      responses:
        '200':
          description: The restored stop.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SpendStop'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/StopNotFound'
        '409':
          $ref: '#/components/responses/StopNotRestorable'
        '502':
          $ref: '#/components/responses/StopRestoreUnavailable'
  /v1/users/me/stops/{stop_id}:
    delete:
      tags: [Users]
      summary: Dismiss a stopped item
      description: >
        Stops listing an item without turning it back on. Allowed while a
        ceiling is still closed.
      operationId: dismissCurrentUserStop
      parameters:
        - $ref: '#/components/parameters/stop_id'
      responses:
        '204':
          description: Dismissed.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/StopNotFound'
  /v1/users/me/statements:
    get:
      tags: [Users]
      summary: List the account's monthly statements
      description: >
        What the account owes for each closed billing month, newest first: the
        subscription fee, prorated by the share of the month each plan was held,
        and runs past the allowance at the plan's overage rate, less what was
        paid at an upgrade. Overage is measured against the highest plan held
        that month.


        A statement is written within an hour of the month closing, once, and
        never changes; `payment_status` follows its charge on the saved card.
        It is not a tax invoice. Months on the Free plan alone, contract plans
        and storage are not stated.
      operationId: listCurrentUserStatements
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum items per page — an integer from 1 to 100 (default 20).
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          required: false
          description: The `next_cursor` of the previous page.
          schema:
            type: string
      responses:
        '200':
          description: A page of statements.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatementPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/users/me/statements/{statement_id}:
    get:
      tags: [Users]
      summary: Get a monthly statement
      description: >
        One of this account's monthly statements, with its lines and total.
        Another account's statement answers `404`.
      operationId: getCurrentUserStatement
      parameters:
        - name: statement_id
          in: path
          required: true
          description: The statement's ID (stm_ prefix).
          schema:
            type: string
      responses:
        '200':
          description: The statement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Statement'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: No such statement on this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/users/me/top-ups:
    post:
      tags: [Users]
      summary: Top up credit
      description: >
        Buys model credit for the caller's account. Once the card is charged the
        full amount is added to `credit_balance_usd` as purchased credit, which
        never expires. The card fee is not deducted. `amount_usd` is what is
        credited; a card issued in Brazil is charged its equivalent in
        Brazilian reais at the Banco Central's latest closing PTAX selling
        rate, and a checkout shows reais to a payer in Brazil.


        With `saved_card: true` the saved card is charged at once and the
        answer is `status: paid`. When the card needs authentication or is
        declined, nothing is charged and the answer falls back to a checkout.


        A checkout (`status: checkout`) is a hosted page: send the caller to
        `checkout_url`. Creating it charges nothing, and one left unpaid lapses
        at `expires_at`. The card paid with there becomes the account's saved
        card, replacing any other. After paying, the browser returns to the
        console's billing page.
      operationId: createCurrentUserTopUp
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TopUpCreate'
      responses:
        '201':
          description: The paid top-up, or the checkout to send the caller to.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TopUp'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: '`saved_card` with no saved card (`payment_method_required`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: >-
            The payment provider could not be reached
            (`payment_provider_error`). Nothing was charged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: >-
            Card payments are not available on this deployment
            (`payments_not_configured`), or the exchange rate for a card
            charged in reais could not be read (`exchange_rate_unavailable`).
            Nothing was charged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/users/me/payment-method:
    get:
      tags: [Users]
      summary: Get the saved card
      description: >
        The card auto-recharge and monthly statements are charged to, as the
        payment provider reports it. Only the brand, last four digits, expiry
        and issuing country are kept here. A card issued in Brazil is charged
        in Brazilian reais, every other card in US dollars.
      operationId: getCurrentUserPaymentMethod
      responses:
        '200':
          description: The saved card.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentMethod'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: The account has no saved card.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags: [Users]
      summary: Start saving a card
      description: >
        A hosted page that saves a card for charges made without the payer
        present: auto-recharge and monthly statements. Send the caller to
        `checkout_url`; nothing is charged. A card saved this way replaces the
        one already saved.
      operationId: createCurrentUserPaymentMethod
      responses:
        '201':
          description: The page to send the caller to.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentMethodSetup'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '502':
          description: The payment provider could not be reached (`payment_provider_error`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Card payments are not available on this deployment (`payments_not_configured`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags: [Users]
      summary: Remove the saved card
      description: Forgets the saved card and turns auto-recharge off.
      operationId: deleteCurrentUserPaymentMethod
      responses:
        '204':
          description: The card is removed.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: The account has no saved card.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Card payments are not available on this deployment (`payments_not_configured`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/users/me/auto-recharge:
    get:
      tags: [Users]
      summary: Get the auto-recharge setting
      description: >
        Whether the credit balance is topped up on the saved card, by how much
        and below what balance. `enabled` is false until a card is saved and
        the setting turned on.
      operationId: getCurrentUserAutoRecharge
      responses:
        '200':
          description: The setting.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoRecharge'
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      tags: [Users]
      summary: Turn auto-recharge on
      description: >
        Top up `amount_usd` on the saved card whenever the credit balance falls
        below `below_usd`. Checked every 15 minutes, at most one charge an
        hour. A declined charge turns it off and emails the account.
      operationId: setCurrentUserAutoRecharge
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AutoRechargeUpdate'
      responses:
        '200':
          description: The setting.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoRecharge'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: The account has no saved card (`payment_method_required`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags: [Users]
      summary: Turn auto-recharge off
      description: >
        Stops topping up the balance automatically. The saved card stays saved.
      operationId: disableCurrentUserAutoRecharge
      responses:
        '200':
          description: The setting, now off.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoRecharge'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/users/me/plan:
    put:
      tags: [Users]
      summary: Change the plan
      description: >
        Choose `free`, `pro` or `business`.


        An upgrade is charged for the price difference over the rest of the
        month, then takes effect; the month's statement nets that payment out.
        The saved card is charged at once, in its currency. With no saved card,
        or when the card needs authentication or is declined, nothing is
        charged and the answer carries a `checkout_url`: a hosted page that
        charges the upgrade and saves the card. The plan changes once it is
        paid; one left unpaid lapses at `expires_at`.


        A downgrade takes effect when the month ends, and `next_plan` names it
        until then. A contract plan is changed by asking us.
      operationId: setCurrentUserPlan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserPlanChangeRequest'
      responses:
        '200':
          description: The plan in effect and the one coming.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPlanChange'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: >-
            A paid plan with nothing to charge now and no saved card for its
            statement (`payment_method_required`), or a contract plan
            (`plan_by_contract`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: The payment provider could not be reached (`payment_provider_error`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: >-
            Card payments are not available on this deployment
            (`payments_not_configured`), or the exchange rate for a card
            charged in reais could not be read (`exchange_rate_unavailable`);
            the plan is unchanged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/users/me/plan/quote:
    get:
      tags: [Users]
      summary: Preview a plan change
      description: >
        What choosing `plan` would charge now: an upgrade's share of the rest
        of the month, else 0. Changes nothing.
      operationId: getCurrentUserPlanQuote
      parameters:
        - name: plan
          in: query
          required: true
          schema:
            type: string
            enum: [free, pro, business]
      responses:
        '200':
          description: The charge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPlanQuote'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
    oauth2:
      type: oauth2
      description: >-
        A connected app's OAuth access token, issued by this API's authorization
        server (discovery: /.well-known/oauth-authorization-server). Its one scope
        carries every operation, confined to the projects the user chose when
        approving the app.
      flows:
        authorizationCode:
          authorizationUrl: https://api.naturali.ai/authorize
          tokenUrl: https://api.naturali.ai/token
          refreshUrl: https://api.naturali.ai/token
          scopes:
            mcp:access: Every operation this API serves, on the projects the grant covers.
  parameters:
    stop_id:
      name: stop_id
      in: path
      required: true
      description: The stop's ID (sst_ prefix).
      schema:
        type: string
  responses:
    StopNotFound:
      description: No such stop on this account, or it was already restored or dismissed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    StopNotRestorable:
      description: The stop cannot be restored now.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    StopRestoreUnavailable:
      description: The runtime could not turn the item back on; the stop stays listed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    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:
    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
        roles:
          type: array
          description: >-
            Roles on naturali itself, not on a project. Empty for almost every
            account; `admin` marks a naturali operator. Changed only through
            `PUT /v1/admin/users/{user_id}/roles`, by a caller holding the role
            that grants each one.
          items:
            type: string
            enum: [admin]
          example: []
        usage_alerts:
          type: boolean
          description: >-
            Whether the account is emailed when its model credit, runs or
            indexed storage reaches 75%, 90% and 100%. On for a new account.
          example: true
        created_at:
          type: string
          format: date-time
          example: '2026-07-17T00:00:00.000Z'
      required: [id, email, email_verified, roles, usage_alerts, created_at]
    UserUpdate:
      type: object
      minProperties: 1
      description: Send at least one field.
      properties:
        name:
          type: string
          nullable: true
          description: Display name; null clears it.
          example: Ana Silva
        usage_alerts:
          type: boolean
          description: >-
            Email the account at 75%, 90% and 100% of its model credit, runs
            and indexed storage.
          example: true
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: >-
            Structured error. Every error response uses this shape, so `code` can
            be read without first checking the type of `error`.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: >-
                Machine-readable error code: lower_snake for an error naturali
                raises (`access_denied`), UPPER_SNAKE for one the runtime reports
                (`RESOURCE_NOT_FOUND`).
              example: access_denied
            message:
              type: string
              description: Human-readable explanation.
              example: Your role in this project does not carry this action.
            details:
              type: object
              additionalProperties: true
              description: >-
                Structured context for an error naturali raises, such as the
                `resource` and `limit` of a `plan_limit_reached`.
            meta:
              type: object
              description: Structured context for an error the runtime reports.
    UserBilling:
      type: object
      required:
        [
          plan,
          next_plan,
          retention_days,
          credit_balance_usd,
          earned_balance_usd,
          paid_balance_usd,
          spend_reconciled_at,
          storage_gb,
          storage_limit_gb,
          storage_sampled_at,
        ]
      properties:
        plan:
          type: string
          description: >-
            The rung of the infra subscription in effect for this account: the
            highest it held this month. `free` for an account with no
            subscription, which is also the least-privileged rung. It gates the
            feature lines and the resource counts of the pricing table.
          enum: [free, pro, business, enterprise]
        next_plan:
          type: string
          description: >-
            The rung the account moves to when the month turns. It differs from
            `plan` only while a downgrade is pending: a downgrade takes effect
            at the end of the month, an upgrade at once.
          enum: [free, pro, business, enterprise]
          example: pro
        retention_days:
          type: integer
          nullable: true
          description: >-
            The longest window a project of this account may keep trace and
            generation content for, in days. A `PATCH` asking for a wider one —
            `null` included, which keeps content indefinitely — is refused with
            `403 plan_limit_reached`; anything shorter is always allowed. On a
            plan whose window a contract sets, this is that contract's figure,
            and `null` means nothing bounds it. A plan change that shortens the
            window applies at the end of the billing cycle, so this reads as the
            widest window held during the current one.
          example: 30
        credit_balance_usd:
          type: number
          description: >-
            Model credit left, in US dollars — granted and purchased credit plus
            the plan's monthly allowance, less managed-model and embedding
            spend. Managed generations and writes that embed are refused while
            this is negative; zero is not. Only as current as
            `spend_reconciled_at`, so read the two together.
          example: 18.42
        earned_balance_usd:
          type: number
          description: >-
            Marketplace earnings: the publisher's share of fees its consumers
            funded, less withdrawals. Never lapses.
          example: 0
        paid_balance_usd:
          type: number
          description: >-
            What may fund a marketplace fee: purchased credit plus earnings,
            less the usage included and granted credit did not cover and the
            fees paid. Included and granted credit never fund a fee, so
            a call to a priced installed tool or agent is refused with
            `402 insufficient_credit` while this is not positive.
          example: 0
        spend_reconciled_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            When spend was last read off the meter and written to the ledger.
            `null` when it has never been read for at least one of the account's
            projects, or the account has none — a balance whose spend has never
            been read is not current as of anything.
          example: '2026-07-17T00:15:00.000Z'
        storage_gb:
          type: number
          description: >-
            Indexed storage the account is holding, in the metered gigabytes the
            ceiling is enforced in — the raw file plus every chunk's text and its
            embedding vector, so not the size of what you uploaded.


            **The account's total, not a project's.** The ceiling is pooled
            across every project the account pays for, so one project may hold
            all of it. Per project, read the `gb_day` component of
            `GET /v1/projects/{project_id}/usage`.
          example: 12.4137
        storage_limit_gb:
          type: number
          nullable: true
          description: >-
            The pool the account's plan allows. An ingest past it is refused with
            `403 plan_limit_reached` and `resource: "storage"`, whose `details`
            carry this figure as `limit` and what you are holding as
            `storage_gb`. Null where a contract sets the ceiling, or where the
            plan sets none.
          example: 30
        storage_sampled_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the **oldest** of the samples behind `storage_gb` was taken —
            the total is only as current as its stalest part. Measured every few
            minutes per project rather than on this request, so read the two
            together, exactly as with the balance and `spend_reconciled_at`.


            Null when a project the account pays for has never been sampled, or
            it has no projects: a total missing a component is not current as of
            anything. The figure itself still reports what the samples that do
            exist add up to.
          example: '2026-07-17T00:15:00.000Z'
    UserUsage:
      type: object
      required: [cycle_from, runs, runs_included, projects]
      properties:
        cycle_from:
          type: string
          format: date-time
          description: >-
            Start of the billing cycle the runs were counted over — the first
            instant of the current UTC calendar month.
          example: '2026-07-01T00:00:00.000Z'
        runs:
          type: integer
          nullable: true
          description: >-
            Runs this cycle across every project the account pays for. `null`
            when at least one project's meter could not be read: a total missing
            a project would understate it, and an understated count read against
            an allowance claims headroom that may not exist. The per-project
            breakdown names which one.
          example: 1284
        runs_included:
          type: integer
          nullable: true
          description: >-
            Runs the account's plan includes per cycle. `null` on a contract
            plan, where the figure is commercial rather than published.
            Exceeding it is refused on `free` and billed as overage on `pro` and
            `business`.
          example: 2000
        projects:
          type: array
          description: >-
            Per project, so a total that has spent its allowance can be traced to
            what spent it. Empty for an account with no projects.
          items:
            $ref: '#/components/schemas/ProjectRuns'
    ProjectRuns:
      type: object
      required: [project_id, runs]
      properties:
        project_id:
          type: string
          x-naturali-ref: projects
          example: proj_V1StGXR8Z5jdHi6B
        runs:
          type: integer
          nullable: true
          description: Runs this cycle; `null` when this project's meter could not be read.
          example: 1284
    SpendStop:
      type: object
      required:
        [
          id,
          project_id,
          kind,
          resource_id,
          reason,
          restorable,
          stopped_at,
          resolution,
          resolved_at,
        ]
      properties:
        id:
          type: string
          example: sst_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          example: proj_V1StGXR8Z5jdHi6B
        kind:
          type: string
          enum: [schedule_trigger, eval_run, orchestration_run, workflow_task]
          description: >-
            What was stopped: a schedule trigger is disabled, an eval run
            cancelled, an orchestration run or a workflow task's automation
            paused.
        resource_id:
          type: string
          description: The stopped trigger, eval run, orchestration run or task.
          example: trg_V1StGXR8Z5jdHi6B
        reason:
          type: string
          enum: [debt, run_allowance, marketplace_fee]
          description: >-
            The ceiling that stopped it: a credit balance below zero, a Free
            plan's runs for the month used up, or marketplace fees beyond
            purchased credit (`paid_balance_usd` below zero).
        restorable:
          type: boolean
          description: False for a cancelled eval run, which can only be dismissed.
        stopped_at:
          type: string
          format: date-time
        resolution:
          type: string
          enum: [restored, dismissed]
          nullable: true
        resolved_at:
          type: string
          format: date-time
          nullable: true
    SpendStopPage:
      type: object
      required: [data, blocked_by, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SpendStop'
        blocked_by:
          type: string
          enum: [debt, run_allowance, marketplace_fee]
          nullable: true
          description: The ceiling still closed, which blocks every restore; null when none is.
        next_cursor:
          type: string
          nullable: true
    StatementLine:
      type: object
      required:
        [
          kind,
          plan,
          description,
          period_from,
          period_to,
          quantity,
          unit,
          unit_price_usd,
          amount_usd,
        ]
      properties:
        kind:
          type: string
          enum:
            [
              subscription,
              run_overage,
              prepaid,
              model_credit_spend,
              marketplace_fees,
              marketplace_earnings,
              platform_take,
            ]
          description: >-
            `subscription`, `run_overage` and `prepaid` make up `total_usd`
            and are charged; `prepaid` is fee already paid by card at an
            upgrade, netted as a negative amount. The other four report
            movements of prepaid credit in the cycle and are not part of it.
        plan:
          type: string
          enum: [free, pro, business, enterprise]
        description:
          type: string
          example: Pro plan, 2026-10-01 to 2026-10-31
        period_from:
          type: string
          format: date-time
        period_to:
          type: string
          format: date-time
          description: Exclusive.
        quantity:
          type: number
          description: >-
            For `subscription`, the share of the month the plan was held; for
            `run_overage`, the runs past the allowance; for `prepaid`, 1.
          example: 1
        unit:
          type: string
          enum: [month, '1,000 runs', payment, USD]
        unit_price_usd:
          type: number
          example: 49
        amount_usd:
          type: number
          example: 49
    Statement:
      type: object
      required:
        [
          id,
          cycle,
          period_from,
          period_to,
          currency,
          runs,
          runs_included,
          lines,
          total_usd,
          payment_status,
          created_at,
        ]
      properties:
        id:
          type: string
          example: stm_V1StGXR8Z5jdHi6B
        cycle:
          type: string
          description: The billing month, `YYYY-MM` in UTC.
          example: '2026-10'
        period_from:
          type: string
          format: date-time
        period_to:
          type: string
          format: date-time
          description: Exclusive.
        currency:
          type: string
          enum: [USD]
        runs:
          type: integer
          description: Runs that month across every project the account pays for.
          example: 30000
        runs_included:
          type: integer
          nullable: true
          description: The allowance overage was measured against.
          example: 25000
        lines:
          type: array
          items:
            $ref: '#/components/schemas/StatementLine'
        total_usd:
          type: number
          description: What is owed, after anything prepaid.
          example: 59
        payment_status:
          type: string
          enum: [unpaid, processing, paid, failed, nothing_due]
          description: >-
            Collection on the saved card. `unpaid` until a charge starts —
            within an hour of the statement, once a card is saved. `failed`
            moves the account to Free when the month ends.
        created_at:
          type: string
          format: date-time
    StatementPage:
      type: object
      required: [data, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Statement'
        next_cursor:
          type: string
          nullable: true
    TopUpCreate:
      type: object
      required: [amount_usd]
      properties:
        amount_usd:
          type: number
          minimum: 5
          maximum: 999999.99
          description: US dollars to buy, in whole cents.
          example: 50
        saved_card:
          type: boolean
          default: false
          description: Charge the saved card now instead of opening a checkout.
    TopUp:
      type: object
      required: [id, amount_usd, status, checkout_url, expires_at]
      properties:
        id:
          type: string
          description: The payment's or the checkout's ID at the payment provider.
          example: cs_test_a1b2c3
        amount_usd:
          type: number
          description: >-
            What is credited once paid: charged in US dollars, or its
            equivalent in reais.
          example: 50
        status:
          type: string
          enum: [paid, checkout]
          description: >-
            `paid`: the saved card was charged and the credit added.
            `checkout`: nothing is charged until the caller pays at
            `checkout_url`.
        checkout_url:
          type: string
          format: uri
          nullable: true
          description: The hosted page that takes the card; `null` when paid.
          example: https://checkout.stripe.com/c/pay/cs_test_a1b2c3
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When an unpaid checkout lapses; `null` when paid.
    PaymentMethod:
      type: object
      required: [brand, last4, exp_month, exp_year, currency, exchange_rate]
      properties:
        brand:
          type: string
          example: visa
        last4:
          type: string
          example: '4242'
        exp_month:
          type: integer
          example: 12
        exp_year:
          type: integer
          example: 2030
        currency:
          type: string
          enum: [usd, brl]
          description: What the card is charged in, from its issuing country.
          example: usd
        exchange_rate:
          type: number
          nullable: true
          description: >-
            Units of `currency` per US dollar for a charge made now: 1 for
            `usd`, the Banco Central's latest closing PTAX selling rate for
            `brl`. `null` while that rate cannot be read.
          example: 1
    PaymentMethodSetup:
      type: object
      required: [id, checkout_url, expires_at]
      properties:
        id:
          type: string
          description: The setup page's ID at the payment provider.
          example: cs_test_a1b2c3
        checkout_url:
          type: string
          format: uri
          description: The hosted page that takes the card.
          example: https://checkout.stripe.com/c/pay/cs_test_a1b2c3
        expires_at:
          type: string
          format: date-time
          description: When an unused page lapses.
    AutoRecharge:
      type: object
      required: [enabled, amount_usd, below_usd, last_attempted_at]
      properties:
        enabled:
          type: boolean
        amount_usd:
          type: number
          nullable: true
          description: What each recharge buys. `null` while off.
          example: 20
        below_usd:
          type: number
          nullable: true
          description: The balance under which a recharge is charged. `null` while off.
          example: 5
        last_attempted_at:
          type: string
          format: date-time
          nullable: true
          description: When a recharge was last charged, whatever its outcome.
    AutoRechargeUpdate:
      type: object
      required: [amount_usd, below_usd]
      properties:
        amount_usd:
          type: number
          minimum: 5
          maximum: 999999.99
          description: US dollars per recharge, in whole cents.
          example: 20
        below_usd:
          type: number
          minimum: 0
          maximum: 999999.99
          description: Recharge when the balance falls below this, in whole cents.
          example: 5
    UserPlanChangeRequest:
      type: object
      required: [plan]
      properties:
        plan:
          type: string
          enum: [free, pro, business]
    UserPlanQuote:
      type: object
      required: [plan, charge_usd]
      properties:
        plan:
          type: string
          enum: [free, pro, business]
        charge_usd:
          type: number
          description: >-
            What `PUT /v1/users/me/plan` would charge now, before any
            conversion to reais; 0 when nothing.
          example: 24.5
    UserPlanChange:
      type: object
      required: [plan, next_plan, charged_usd, checkout_url, expires_at]
      properties:
        plan:
          type: string
          enum: [free, pro, business, enterprise]
          description: The plan in effect.
        next_plan:
          type: string
          enum: [free, pro, business, enterprise]
          description: The plan from next month.
        charged_usd:
          type: number
          description: What the saved card was charged now; 0 when nothing was.
          example: 24.5
        checkout_url:
          type: string
          format: uri
          nullable: true
          description: >-
            The hosted page the upgrade waits on, which charges it and saves
            the card; `null` when nothing waits.
          example: null
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When an unpaid `checkout_url` lapses.
          example: null
