---
components:
  responses:
    InternalServerError:
      content:
        application/json:
          example:
            errors:
              - detail: an unexpected error occurred while processing the request
                title: Internal Server Error
          schema:
            $ref: '#/components/schemas/Error'
      description: Unexpected error while processing the request.
    TooManyRequests:
      content:
        application/json:
          example:
            errors:
              - detail: Rate limit exceeded. Retry after 7 seconds.
                meta:
                  limit: 600
                  remaining: 0
                  reset_at: 2026-05-27T12:00:07Z
                  retry_after_s: 7
                  scope: tenant
                title: Too Many Requests
          schema:
            $ref: '#/components/schemas/TooManyRequests'
      description: Rate limit exceeded.
      headers:
        RateLimit:
          description: 'IETF draft-11: "<policy>";r=<remaining>;t=<seconds-until-reset>'
          schema:
            type: string
          style: simple
        RateLimit-Policy:
          description: 'IETF draft-11: "<policy>";q=<limit>;w=<window-seconds>'
          schema:
            type: string
          style: simple
        Retry-After:
          description: Seconds until the current window resets.
          schema:
            example: 7
            type: integer
          style: simple
        X-RateLimit-Limit:
          description: Total tokens in the active bucket.
          schema:
            example: 600
            type: integer
          style: simple
        X-RateLimit-Remaining:
          description: Tokens remaining in the current window.
          schema:
            example: 0
            type: integer
          style: simple
        X-RateLimit-Reset:
          description: Unix epoch seconds at which the window resets.
          schema:
            example: 1748347207
            type: integer
          style: simple
    ValidationError:
      content:
        application/json:
          example:
            errors:
              - detail: Invalid format. Expected ~r/^-?\d+(\.\d+)?$/
                source:
                  pointer: /quantity/value
                title: Invalid value
          schema:
            $ref: '#/components/schemas/Error'
      description: Request did not match the schema.
  schemas:
    Amount:
      description: Quantity of base asset. Must be strictly positive.
      example: '0.5'
      pattern: ^\d+(\.\d+)?$
      title: Amount
      type: string
    Asset:
      description: Asset identifier. Uppercase letters only — e.g. `BTC`, `ETH`, `USD`. Used as the base or quote component of a trading symbol.
      example: BTC
      pattern: ^[A-Z]+$
      title: Asset
      type: string
    Balance:
      properties:
        asset:
          type: string
        balance:
          $ref: '#/components/schemas/Decimal'
        human_readable_balance:
          description: |
            Locale-formatted balance with currency, suitable for display.
            Format follows CLDR rules for ISO 4217 currencies (e.g. `US$187.62`)
            and falls back to `<amount> <CODE>` for crypto assets (e.g. `0.5 BTC`).
          example: US$187.62
          type: string
      required:
        - balance
        - asset
        - human_readable_balance
      title: Balance
      type: object
    Decimal:
      description: |
        Arbitrary-precision decimal number serialized as a string to avoid
        IEEE 754 precision loss. Matches an optional sign followed by digits
        and an optional fractional part.
      example: '12345.6789'
      pattern: ^-?\d+(\.\d+)?$
      title: Decimal
      type: string
    Error:
      description: |
        JSON:API-shaped error envelope. Returned for all 4xx responses.
        May contain one or more error objects.
      properties:
        errors:
          items:
            properties:
              detail:
                description: Human-readable explanation specific to this occurrence.
                type: string
              source:
                description: Where in the request the error originated. Optional.
                properties:
                  pointer:
                    description: JSON Pointer (RFC 6901) to the offending field.
                    type: string
                type: object
              title:
                description: Short, human-readable summary of the problem.
                type: string
            required:
              - title
              - detail
            type: object
          minItems: 1
          type: array
      required:
        - errors
      title: Error
      type: object
    ExecuteQuoteRequest:
      description: Execute request body. No fields are required from API clients.
      properties: {}
      title: ExecuteQuoteRequest
      type: object
    Price:
      description: Total price in quote asset for the full amount.
      example: '33945.06'
      pattern: ^-?\d+(\.\d+)?$
      title: Price
      type: string
    Quote:
      description: Tagged union of every possible lifecycle state of a quote.
      discriminator:
        mapping:
          cancelled: '#/components/schemas/QuoteCancelled'
          executed: '#/components/schemas/QuoteExecuted'
          expired: '#/components/schemas/QuoteExpired'
          failed: '#/components/schemas/QuoteFailed'
          processing: '#/components/schemas/QuoteProcessing'
          quoted: '#/components/schemas/QuoteQuoted'
          rejected: '#/components/schemas/QuoteRejected'
          requested: '#/components/schemas/QuoteRequested'
        propertyName: status
      oneOf:
        - $ref: '#/components/schemas/QuoteRequested'
        - $ref: '#/components/schemas/QuoteQuoted'
        - $ref: '#/components/schemas/QuoteRejected'
        - $ref: '#/components/schemas/QuoteProcessing'
        - $ref: '#/components/schemas/QuoteExecuted'
        - $ref: '#/components/schemas/QuoteFailed'
        - $ref: '#/components/schemas/QuoteExpired'
        - $ref: '#/components/schemas/QuoteCancelled'
      title: Quote
    QuoteBase:
      properties:
        amount:
          $ref: '#/components/schemas/Amount'
        base_asset:
          $ref: '#/components/schemas/Asset'
        quote_asset:
          $ref: '#/components/schemas/Asset'
        quote_id:
          type: string
        requested_at:
          allOf:
            - $ref: '#/components/schemas/Timestamp'
          description: When the client submitted the quote request.
        side:
          $ref: '#/components/schemas/QuoteSide'
        status:
          $ref: '#/components/schemas/QuoteStatus'
        symbol:
          $ref: '#/components/schemas/Symbol'
        symbol_type:
          $ref: '#/components/schemas/SymbolType'
      required:
        - quote_id
        - status
        - symbol
        - base_asset
        - quote_asset
        - symbol_type
        - amount
        - side
        - requested_at
      title: QuoteBase
      type: object
    QuoteCancelled:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - $ref: '#/components/schemas/QuotedFields'
        - properties:
            status:
              enum:
                - cancelled
              type: string
          type: object
      title: QuoteCancelled
    QuoteExecuted:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - $ref: '#/components/schemas/QuotedFields'
        - properties:
            executed_at:
              allOf:
                - $ref: '#/components/schemas/Timestamp'
              description: When the trade was executed at the quoted price.
            status:
              enum:
                - executed
              type: string
          required:
            - executed_at
          type: object
      title: QuoteExecuted
    QuoteExpired:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - $ref: '#/components/schemas/QuotedFields'
        - properties:
            status:
              enum:
                - expired
              type: string
          type: object
      title: QuoteExpired
    QuoteFailed:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - $ref: '#/components/schemas/QuotedFields'
        - properties:
            reason:
              type: string
            status:
              enum:
                - failed
              type: string
          required:
            - reason
          type: object
      description: |
        Terminal failure at execution time — the quote was accepted but
        execution could not be completed. The client should retry the full
        RFQ flow (request a new quote and execute). If failures persist, contact
        support@stelaxis.com.
      title: QuoteFailed
    QuoteProcessing:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - $ref: '#/components/schemas/QuotedFields'
        - properties:
            status:
              enum:
                - processing
              type: string
          type: object
      title: QuoteProcessing
    QuoteQuoted:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - $ref: '#/components/schemas/QuotedFields'
        - properties:
            status:
              enum:
                - quoted
              type: string
          type: object
      title: QuoteQuoted
    QuoteRejected:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - properties:
            reason:
              type: string
            status:
              enum:
                - rejected
              type: string
          required:
            - reason
          type: object
      description: |
        Terminal failure at quote time — the request was refused. The client
        should not retry the same request; consider adjusting parameters.
      title: QuoteRejected
    QuoteRequest:
      properties:
        amount:
          $ref: '#/components/schemas/Amount'
        client_quote_id:
          description: |
            Optional client-supplied ID for the quote. If omitted, the server
            generates a UUIDv4. If provided and already known, the request is
            rejected with 409. This is **not** an idempotency key — replaying
            the same `client_quote_id` will not return the original response.
          type: string
        settlement_hours:
          description: Optional expected settlement duration in hours.
          example: 0
          minimum: 0
          type: integer
        side:
          $ref: '#/components/schemas/QuoteSide'
        symbol:
          $ref: '#/components/schemas/Symbol'
        ttl_seconds:
          description: Optional requested time-to-live for the quote in seconds.
          example: 30
          minimum: 1
          type: integer
      required:
        - symbol
        - amount
        - side
      title: QuoteRequest
      type: object
    QuoteRequested:
      allOf:
        - $ref: '#/components/schemas/QuoteBase'
        - properties:
            status:
              enum:
                - requested
              type: string
          type: object
      title: QuoteRequested
    QuoteSide:
      enum:
        - buy
        - sell
      title: QuoteSide
      type: string
    QuoteStatus:
      description: |
        Lifecycle status of a quote.

        Two distinct rejection states are used to disambiguate where the failure
        happened:

        - `rejected` — terminal failure at quote time (e.g. price not available,
          ineligible pair). The client should treat this as a refusal of the
          request itself; retrying the same request is unlikely to succeed.
        - `failed` — terminal failure at execution time (e.g. liquidity withdrawn,
          downstream venue error). The client should retry the full RFQ flow
          (request a new quote and execute it). Persistent `failed` results
          warrant contacting support@stelaxis.com.
        - `cancelled` — a stuck `processing` quote that an operator resolved as
          terminal from the back-office. No execute attempt can ever settle against
          it, on any channel.

        Transitions:
          `requested` → `quoted` | `rejected`
          `quoted`    → `processing` | `expired`
          `processing`→ `executed` | `failed` | `cancelled`
      enum:
        - requested
        - quoted
        - rejected
        - processing
        - executed
        - failed
        - expired
        - cancelled
      title: QuoteStatus
      type: string
    QuotedFields:
      properties:
        commission:
          allOf:
            - $ref: '#/components/schemas/Price'
          description: |
            Commission charged on execution, denominated in `commission_currency`.
            `"0"` when no commission applies.
        commission_currency:
          description: Asset the commission is denominated in — the symbol's quote currency.
          example: USD
          type: string
        expires_at:
          allOf:
            - $ref: '#/components/schemas/Timestamp'
          description: When the quoted price stops being valid.
        price:
          $ref: '#/components/schemas/Price'
        quoted_at:
          allOf:
            - $ref: '#/components/schemas/Timestamp'
          description: When the price was determined for the request.
        settlement_hours:
          minimum: 0
          type: integer
        ttl_seconds:
          description: |
            Time-to-live for the quote in whole seconds. Derived from
            `quoted_at` and `expires_at`, so `expires_at == quoted_at + ttl_seconds`
            always holds at second precision.
          minimum: 1
          type: integer
      required:
        - price
        - commission
        - commission_currency
        - quoted_at
        - expires_at
        - ttl_seconds
        - settlement_hours
      title: QuotedFields
      type: object
    Symbol:
      description: Instrument identifier. Spot pairs use the canonical CCY1-CCY2 format (e.g. BTC-USD); contracts-for-difference append a .CFD suffix (e.g. BTC-USD.CFD).
      example: BTC-USD
      pattern: ^[A-Z]+-[A-Z]+(\.CFD)?$
      title: Symbol
      type: string
    SymbolType:
      description: Instrument type. `spot` is a plain pair (`CCY1-CCY2`); `cfd` is a contracts-for-difference instrument with a `.CFD`-suffixed symbol (`CCY1-CCY2.CFD`). Always consistent with `symbol`.
      enum:
        - spot
        - cfd
      example: spot
      title: SymbolType
      type: string
    Timestamp:
      description: |
        ISO-8601 datetime in UTC with millisecond precision and `Z` suffix.
      example: 2026-05-01T12:34:56.789Z
      format: date-time
      pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$
      title: Timestamp
      type: string
    TooManyRequests:
      description: |
        Rate limit exceeded. The response headers carry the authoritative
        rate-limit state; the body is for humans.
      properties:
        errors:
          items:
            properties:
              detail:
                example: Rate limit exceeded. Retry after 7 seconds.
                type: string
              meta:
                properties:
                  limit:
                    description: Total tokens in the active bucket.
                    example: 600
                    type: integer
                  remaining:
                    description: Tokens remaining in the current window.
                    example: 0
                    type: integer
                  reset_at:
                    description: UTC timestamp at which the window resets.
                    example: 2026-05-27T12:00:07Z
                    format: date-time
                    type: string
                  retry_after_s:
                    description: Seconds until the window resets.
                    example: 7
                    type: integer
                  scope:
                    description: Which bucket was exhausted.
                    enum:
                      - tenant
                    type: string
                required:
                  - limit
                  - remaining
                  - reset_at
                  - retry_after_s
                  - scope
                type: object
              title:
                example: Too Many Requests
                type: string
            required:
              - title
              - detail
              - meta
            type: object
          minItems: 1
          type: array
      required:
        - errors
      title: TooManyRequests
      type: object
    TradeReport:
      description: |
        An asynchronous orders-export job. Submit with `POST /trade-reports`, then
        poll `GET /trade-reports/{report_id}` until `status` is a terminal state.
        While `pending` or `running` the result fields are null; when `succeeded`,
        `download_url` and `row_count` are populated; when `failed`, `error` is.
      properties:
        client_job_id:
          description: The caller-supplied idempotency handle, if one was given.
          nullable: true
          type: string
        completed_at:
          description: When the export reached a terminal state, or null if not yet.
          example: 2026-06-01T00:04:12.784Z
          format: date-time
          nullable: true
          type: string
        download_url:
          description: |
            Absolute URL to download the ZIP archive (`orders-<from>-<to>.csv`
            inside). Populated only when `succeeded`. The download is authenticated
            with the same bearer token as the rest of the API.
          example: https://api.stelaxis.com/v1/trade-reports/4217/download
          nullable: true
          type: string
        error:
          description: Populated when `status` is `failed`.
          nullable: true
          properties:
            message:
              description: Client-safe failure message.
              type: string
          required:
            - message
          type: object
        order_status:
          description: The order-status filter applied to this export, or null for all statuses.
          example: traded
          nullable: true
          type: string
        report_id:
          description: |
            Server-assigned id; use it to poll status and download. A decimal integer
            serialized as a **string** — the underlying value can exceed JavaScript's
            safe-integer range, so it must not be parsed as a JSON number.
          example: '4217'
          type: string
        row_count:
          description: Number of rows in the export. Populated when `succeeded`.
          example: 43102934
          nullable: true
          type: integer
        status:
          description: |
            Lifecycle state. `pending` → `running` → `succeeded` | `failed`.
            Terminal states are `succeeded` and `failed`.
          enum:
            - pending
            - running
            - succeeded
            - failed
          type: string
        submitted_at:
          allOf:
            - $ref: '#/components/schemas/Timestamp'
          description: When the export was submitted.
        symbol:
          description: The symbol filter applied to this export, or null for all symbols. Echoes the request so a report is self-describing.
          example: BTC-USD
          nullable: true
          type: string
        time_range:
          description: The requested export window `[start, end)` in UTC.
          properties:
            end:
              $ref: '#/components/schemas/Timestamp'
            start:
              $ref: '#/components/schemas/Timestamp'
          required:
            - start
            - end
          type: object
      required:
        - report_id
        - status
        - time_range
        - submitted_at
      title: TradeReport
      type: object
    TradeReportRequest:
      description: |
        Requests an asynchronous orders export over an **inclusive** date range.
        Both bounds are calendar dates in UTC (`YYYY-MM-DD`); the export covers
        every order created from the start of `start_date` through the end of
        `end_date`. The range must not exceed 366 days and `end_date` must not be
        before `start_date`.

        The optional `symbol` and `order_status` filters narrow the exported orders.
      properties:
        client_job_id:
          description: |
            Optional caller-supplied idempotency handle (max 255 chars). If a report
            with the same `client_job_id` already exists for your entity, the request
            is rejected with `409 Conflict` and the response body carries the existing
            report — so a network retry is safe. Mirrors RFQ's `client_quote_id`.
          example: eod-2026-05-31
          maxLength: 255
          type: string
        end_date:
          description: Last day to include (inclusive), UTC.
          example: 2026-05-31
          format: date
          type: string
        order_status:
          description: Optional. Restrict the export to orders in one status.
          enum:
            - pending
            - traded
            - rejected
          example: traded
          type: string
        start_date:
          description: First day to include (inclusive), UTC.
          example: 2026-05-01
          format: date
          type: string
        symbol:
          description: |
            Optional. Restrict the export to one instrument (canonical `BASE-QUOTE`,
            e.g. `BTC-USD`). Must be a symbol Stelaxis offers, else `422`.
          example: BTC-USD
          pattern: ^[A-Z]+-[A-Z]+(\.CFD)?$
          type: string
      required:
        - start_date
        - end_date
      title: TradeReportRequest
      type: object
    TradingPair:
      example:
        base_asset: BTC
        quote_asset: USD
        symbol: BTC-USD
      properties:
        base_asset:
          $ref: '#/components/schemas/Asset'
        quote_asset:
          $ref: '#/components/schemas/Asset'
        symbol:
          $ref: '#/components/schemas/Symbol'
      required:
        - symbol
        - base_asset
        - quote_asset
      title: TradingPair
      type: object
  securitySchemes:
    bearerHttpAuthentication:
      bearerFormat: JWT
      description: Bearer token using a JWT
      scheme: bearer
      type: http
info:
  contact:
    email: support@stelaxis.com
    name: Stelaxis Support
  description: |
    Stelaxis client-facing REST API.

    For authorization, supply JWT token as a bearer one. Reach out to support to obtain your JWT token.

    **Timestamps.** All `date-time` fields are ISO-8601 strings in UTC with
    millisecond precision and a `Z` suffix — e.g. `2026-05-01T12:34:56.789Z`.
    Local-time offsets are not used; clients should parse and render in their
    own timezone if needed.

    **Rate limiting.** Every authenticated request consumes one token from a
    per-tenant bucket (600 req / 60 s by default). All responses carry
    `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`
    headers. A 429 response additionally carries `Retry-After`
    (delta seconds).
  title: Stelaxis API
  version: '1.1'
openapi: 3.0.0
paths:
  /balances:
    get:
      callbacks: {}
      operationId: getBalances
      parameters: []
      responses:
        '200':
          content:
            application/json:
              schema:
                example:
                  - asset: BTC
                    balance: '0'
                    human_readable_balance: 0 BTC
                  - asset: ETH
                    balance: '-1.377'
                    human_readable_balance: -1.377 ETH
                  - asset: USD
                    balance: '187.62'
                    human_readable_balance: US$187.62
                items:
                  $ref: '#/components/schemas/Balance'
                type: array
          description: List of balances per asset.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              examples:
                ip_not_whitelisted:
                  summary: Caller IP is not in the entity's whitelist
                  value:
                    errors:
                      - detail: IP 198.51.100.42 is not whitelisted
                        title: Forbidden
                no_whitelist_configured:
                  summary: Entity has no IP whitelist entries configured
                  value:
                    errors:
                      - detail: No IP whitelist configured
                        title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Get account balances
      tags:
        - accounts
  /rfq/quotes:
    post:
      callbacks: {}
      description: |
        Request a one-way quote for an asset pair.

        This call blocks until the quote is either `quoted` or `rejected`.

        The optional `client_quote_id` lets the caller supply their own ID for the
        quote. If provided and not already known, it is used as-is; if already known,
        the request is rejected with 409. If omitted, the server generates a UUIDv4.
      operationId: requestQuote
      parameters: []
      requestBody:
        content:
          application/json:
            example:
              amount: '0.5'
              client_quote_id: order-2026-05-01-0042
              settlement_hours: 0
              side: buy
              symbol: BTC-USD
              ttl_seconds: 30
            schema:
              $ref: '#/components/schemas/QuoteRequest'
        description: Quote request
        required: true
      responses:
        '200':
          content:
            application/json:
              examples:
                quoted:
                  summary: Quote returned with a price
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440000
                    quoted_at: 2026-05-01T12:34:56.789Z
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: quoted
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
                rejected:
                  summary: Quote refused
                  value:
                    amount: '10000'
                    base_asset: BTC
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440001
                    reason: size exceeds available liquidity
                    requested_at: 2026-05-01T12:34:56.123Z
                    side: buy
                    status: rejected
                    symbol: BTC-USD
                    symbol_type: spot
              schema:
                discriminator:
                  mapping:
                    quoted: '#/components/schemas/QuoteQuoted'
                    rejected: '#/components/schemas/QuoteRejected'
                  propertyName: status
                oneOf:
                  - $ref: '#/components/schemas/QuoteQuoted'
                  - $ref: '#/components/schemas/QuoteRejected'
          description: Quote response — `quoted` or `rejected`.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              examples:
                ip_not_whitelisted:
                  summary: Caller IP is not in the entity's whitelist
                  value:
                    errors:
                      - detail: IP 198.51.100.42 is not whitelisted
                        title: Forbidden
                no_whitelist_configured:
                  summary: Entity has no IP whitelist entries configured
                  value:
                    errors:
                      - detail: No IP whitelist configured
                        title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '409':
          content:
            application/json:
              example:
                errors:
                  - detail: "quote with client_quote_id 'order-2026-05-01-0042' already exists"
                    source:
                      pointer: /client_quote_id
                    title: Conflict
              schema:
                $ref: '#/components/schemas/Error'
          description: A quote with the supplied `client_quote_id` already exists.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Request a quote
      tags:
        - rfq
  /rfq/quotes/{quote_id}:
    get:
      callbacks: {}
      description: Check the status of a quote already requested.
      operationId: getQuoteStatus
      parameters:
        - description: ''
          in: path
          name: quote_id
          required: true
          schema:
            type: string
      responses:
        '200':
          content:
            application/json:
              examples:
                executed:
                  summary: Trade settled successfully
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    executed_at: 2026-05-01T12:35:01.456Z
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440000
                    quoted_at: 2026-05-01T12:34:56.789Z
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: executed
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
                expired:
                  summary: Quote was not executed before TTL elapsed
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440000
                    quoted_at: 2026-05-01T12:34:56.789Z
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: expired
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
                failed:
                  summary: Execution failed downstream
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440002
                    quoted_at: 2026-05-01T12:34:56.789Z
                    reason: downstream venue rejected execution
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: failed
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
                processing:
                  summary: Execution accepted, settlement in progress
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440000
                    quoted_at: 2026-05-01T12:34:56.789Z
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: processing
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
                quoted:
                  summary: Quote is live and awaiting execution
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440000
                    quoted_at: 2026-05-01T12:34:56.789Z
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: quoted
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
                rejected:
                  summary: Quote was refused at request time
                  value:
                    amount: '10000'
                    base_asset: BTC
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440001
                    reason: size exceeds available liquidity
                    requested_at: 2026-05-01T12:34:56.123Z
                    side: buy
                    status: rejected
                    symbol: BTC-USD
                    symbol_type: spot
              schema:
                $ref: '#/components/schemas/Quote'
          description: Quote object in any lifecycle state.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              examples:
                ip_not_whitelisted:
                  summary: Caller IP is not in the entity's whitelist
                  value:
                    errors:
                      - detail: IP 198.51.100.42 is not whitelisted
                        title: Forbidden
                no_whitelist_configured:
                  summary: Entity has no IP whitelist entries configured
                  value:
                    errors:
                      - detail: No IP whitelist configured
                        title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '404':
          content:
            application/json:
              example:
                errors:
                  - detail: quote not found
                    title: Not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Quote not found.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Get quote status
      tags:
        - rfq
  /rfq/quotes/{quote_id}/execute:
    post:
      callbacks: {}
      operationId: executeQuote
      parameters:
        - description: ''
          in: path
          name: quote_id
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecuteQuoteRequest'
        description: Execute request body. No fields are required from API clients.
        required: false
      responses:
        '200':
          content:
            application/json:
              examples:
                executed:
                  summary: Trade executed at the quoted price
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    executed_at: 2026-05-01T12:35:01.456Z
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440000
                    quoted_at: 2026-05-01T12:34:56.789Z
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: executed
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
                failed:
                  summary: Execution failed; client should re-quote
                  value:
                    amount: '0.5'
                    base_asset: BTC
                    expires_at: 2026-05-01T12:35:26.789Z
                    price: '33945.06'
                    quote_asset: USD
                    quote_id: 550e8400-e29b-41d4-a716-446655440002
                    quoted_at: 2026-05-01T12:34:56.789Z
                    reason: downstream venue rejected execution
                    requested_at: 2026-05-01T12:34:56.123Z
                    settlement_hours: 0
                    side: buy
                    status: failed
                    symbol: BTC-USD
                    symbol_type: spot
                    ttl_seconds: 30
              schema:
                discriminator:
                  mapping:
                    executed: '#/components/schemas/QuoteExecuted'
                    failed: '#/components/schemas/QuoteFailed'
                  propertyName: status
                oneOf:
                  - $ref: '#/components/schemas/QuoteExecuted'
                  - $ref: '#/components/schemas/QuoteFailed'
          description: |
            Execution response — `executed` on success, or `failed` if execution
            could not be completed (e.g. liquidity withdrawn, downstream venue
            error). On `failed`, the client should retry the full RFQ flow
            (request a new quote and execute it). If failures persist, contact
            support@stelaxis.com.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              examples:
                ip_not_whitelisted:
                  summary: Caller IP is not in the entity's whitelist
                  value:
                    errors:
                      - detail: IP 198.51.100.42 is not whitelisted
                        title: Forbidden
                no_whitelist_configured:
                  summary: Entity has no IP whitelist entries configured
                  value:
                    errors:
                      - detail: No IP whitelist configured
                        title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '404':
          content:
            application/json:
              example:
                errors:
                  - detail: quote not found
                    title: Not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Quote not found.
        '409':
          content:
            application/json:
              examples:
                already_executed:
                  summary: Quote was already executed
                  value:
                    errors:
                      - detail: quote already executed
                        title: Conflict
                cancelled:
                  summary: Quote was cancelled by an operator
                  value:
                    errors:
                      - detail: quote is in cancelled state and cannot be executed
                        title: Conflict
                expired:
                  summary: Quote expired before execution
                  value:
                    errors:
                      - detail: quote expired before execution
                        title: Conflict
                processing:
                  summary: Execution already in progress
                  value:
                    errors:
                      - detail: quote is already being processed
                        title: Conflict
                rejected:
                  summary: Quote was rejected at request time
                  value:
                    errors:
                      - detail: quote was rejected and cannot be executed
                        title: Conflict
                streaming_acquired:
                  summary: Trading account is in use by a streaming session
                  value:
                    errors:
                      - detail: client trading account is in use by a streaming session
                        title: Conflict
              schema:
                $ref: '#/components/schemas/Error'
          description: |
            The quote is not in an executable state. A quote can only be
            executed while its status is `quoted`. Calling execute on a
            quote that is `requested`, `processing`, `executed`, `failed`,
            `rejected`, `expired`, or `cancelled` returns 409. The current
            state can be inspected via `GET /rfq/quotes/{quote_id}`.

            A 409 is also returned when the target trading account is
            currently in use by an active streaming session. RFQ and
            streaming cannot operate on the same account at the same time;
            close the streaming session before retrying.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Execute a quote
      tags:
        - rfq
  /rfq/trading-pairs:
    get:
      callbacks: {}
      description: Get a list of trading pairs you are eligible to trade.
      operationId: getTradingPairs
      parameters: []
      responses:
        '200':
          content:
            application/json:
              schema:
                example:
                  - base_asset: BTC
                    quote_asset: USD
                    symbol: BTC-USD
                  - base_asset: ETH
                    quote_asset: USD
                    symbol: ETH-USD
                items:
                  $ref: '#/components/schemas/TradingPair'
                type: array
          description: List of trading pairs.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              examples:
                ip_not_whitelisted:
                  summary: Caller IP is not in the entity's whitelist
                  value:
                    errors:
                      - detail: IP 198.51.100.42 is not whitelisted
                        title: Forbidden
                no_whitelist_configured:
                  summary: Entity has no IP whitelist entries configured
                  value:
                    errors:
                      - detail: No IP whitelist configured
                        title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Get trading pairs
      tags:
        - rfq
  /trade-reports:
    get:
      callbacks: {}
      description: Your entity's current and recent orders-export jobs, newest first.
      operationId: listTradeReports
      parameters: []
      responses:
        '200':
          content:
            application/json:
              example:
                - client_job_id: eod-2026-05-31
                  completed_at: 2026-06-01T00:04:12.784Z
                  download_url: https://api.stelaxis.com/v1/trade-reports/4217/download
                  error:
                  order_status:
                  report_id: '4217'
                  row_count: 43102934
                  status: succeeded
                  submitted_at: 2026-06-01T00:00:03.128Z
                  symbol: BTC-USD
                  time_range:
                    end: 2026-06-01T00:00:00.000Z
                    start: 2026-05-01T00:00:00.000Z
                - client_job_id: eod-2026-05-31
                  completed_at:
                  download_url:
                  error:
                  order_status:
                  report_id: '4217'
                  row_count:
                  status: pending
                  submitted_at: 2026-06-01T00:00:03.128Z
                  symbol: BTC-USD
                  time_range:
                    end: 2026-06-01T00:00:00.000Z
                    start: 2026-05-01T00:00:00.000Z
              schema:
                items:
                  $ref: '#/components/schemas/TradeReport'
                type: array
          description: List of export jobs.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              example:
                errors:
                  - detail: IP 198.51.100.42 is not whitelisted
                    title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: List orders exports
      tags:
        - trade-reports
    post:
      callbacks: {}
      description: |
        Submits an asynchronous orders export over an inclusive UTC date range.
        Optional `symbol` and `order_status` filters narrow the exported orders.
        Returns `202 Accepted` with a `pending` job; poll
        `GET /trade-reports/{report_id}` for status.

        Supplying `client_job_id` makes the submission idempotent: a duplicate
        returns `409 Conflict` with the existing job rather than starting a new one.
      operationId: requestTradeReport
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TradeReportRequest'
        description: Export request
        required: true
      responses:
        '202':
          content:
            application/json:
              example:
                client_job_id: eod-2026-05-31
                completed_at:
                download_url:
                error:
                order_status:
                report_id: '4217'
                row_count:
                status: pending
                submitted_at: 2026-06-01T00:00:03.128Z
                symbol: BTC-USD
                time_range:
                  end: 2026-06-01T00:00:00.000Z
                  start: 2026-05-01T00:00:00.000Z
              schema:
                $ref: '#/components/schemas/TradeReport'
          description: Export accepted; job created in `pending` state.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              example:
                errors:
                  - detail: IP 198.51.100.42 is not whitelisted
                    title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '409':
          content:
            application/json:
              example:
                client_job_id: eod-2026-05-31
                completed_at: 2026-06-01T00:04:12.784Z
                download_url: https://api.stelaxis.com/v1/trade-reports/4217/download
                error:
                order_status:
                report_id: '4217'
                row_count: 43102934
                status: succeeded
                submitted_at: 2026-06-01T00:00:03.128Z
                symbol: BTC-USD
                time_range:
                  end: 2026-06-01T00:00:00.000Z
                  start: 2026-05-01T00:00:00.000Z
              schema:
                $ref: '#/components/schemas/TradeReport'
          description: A report with the supplied `client_job_id` already exists; its current state is returned.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Request an orders export
      tags:
        - trade-reports
  /trade-reports/{report_id}:
    get:
      callbacks: {}
      description: Current state of an export job. On `succeeded`, includes `download_url`.
      operationId: getTradeReport
      parameters:
        - description: ''
          in: path
          name: report_id
          required: true
          schema:
            pattern: ^[1-9][0-9]{0,18}$
            type: string
      responses:
        '200':
          content:
            application/json:
              examples:
                failed:
                  summary: Generation failed
                  value:
                    client_job_id: eod-2026-05-31
                    completed_at: 2026-06-01T00:04:12.784Z
                    download_url:
                    error:
                      message: Report generation failed.
                    order_status:
                    report_id: '4217'
                    row_count:
                    status: failed
                    submitted_at: 2026-06-01T00:00:03.128Z
                    symbol: BTC-USD
                    time_range:
                      end: 2026-06-01T00:00:00.000Z
                      start: 2026-05-01T00:00:00.000Z
                pending:
                  summary: Still queued
                  value:
                    client_job_id: eod-2026-05-31
                    completed_at:
                    download_url:
                    error:
                    order_status:
                    report_id: '4217'
                    row_count:
                    status: pending
                    submitted_at: 2026-06-01T00:00:03.128Z
                    symbol: BTC-USD
                    time_range:
                      end: 2026-06-01T00:00:00.000Z
                      start: 2026-05-01T00:00:00.000Z
                succeeded:
                  summary: Ready to download
                  value:
                    client_job_id: eod-2026-05-31
                    completed_at: 2026-06-01T00:04:12.784Z
                    download_url: https://api.stelaxis.com/v1/trade-reports/4217/download
                    error:
                    order_status:
                    report_id: '4217'
                    row_count: 43102934
                    status: succeeded
                    submitted_at: 2026-06-01T00:00:03.128Z
                    symbol: BTC-USD
                    time_range:
                      end: 2026-06-01T00:00:00.000Z
                      start: 2026-05-01T00:00:00.000Z
              schema:
                $ref: '#/components/schemas/TradeReport'
          description: The export job.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              example:
                errors:
                  - detail: IP 198.51.100.42 is not whitelisted
                    title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '404':
          content:
            application/json:
              example:
                errors:
                  - detail: trade report not found
                    title: Not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Export not found.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Get orders export status
      tags:
        - trade-reports
  /trade-reports/{report_id}/download:
    get:
      callbacks: {}
      description: |
        Streams the ZIP archive (`orders-<from>-<to>.csv` inside) for a `succeeded`
        export. This is the target of the `download_url` returned by
        `GET /trade-reports/{report_id}`.
      operationId: downloadTradeReport
      parameters:
        - description: ''
          in: path
          name: report_id
          required: true
          schema:
            pattern: ^[1-9][0-9]{0,18}$
            type: string
      responses:
        '200':
          content:
            application/zip:
              schema:
                format: binary
                type: string
          description: The ZIP archive.
        '401':
          content:
            application/json:
              example:
                errors:
                  - detail: 'JWT validation failed: token expired'
                    title: Unauthorized
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication failed — missing, invalid, or expired bearer token.
        '403':
          content:
            application/json:
              example:
                errors:
                  - detail: IP 198.51.100.42 is not whitelisted
                    title: Forbidden
              schema:
                $ref: '#/components/schemas/Error'
          description: Caller IP is not allowed to access the API for this entity.
        '404':
          content:
            application/json:
              example:
                errors:
                  - detail: trade report not found or not ready
                    title: Not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Export not found or not ready to download.
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Download an orders export
      tags:
        - trade-reports
security:
  - bearerHttpAuthentication: []
servers:
  - url: https://api.staging.stlxs.dev/v1
    variables: {}
tags: []
