openapi: 3.1.0
info:
  title: Traderz Labs — Signal Ingestion API
  version: 1.0.0
  description: |
    Receives trading signals from authenticated traders and stores them in
    Cloudflare D1. Each trader authenticates with an API key
    (`Authorization: Bearer <key>` or `X-API-Key: <key>`).
servers:
  - url: http://localhost:8787
    description: Local development (wrangler dev)
tags:
  - name: signals
  - name: health
paths:
  /v1/signals:
    post:
      tags: [signals]
      summary: Ingest a trading signal
      operationId: createSignal
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSignalRequest'
            examples:
              crypto:
                summary: Open a long on ETH/USDT
                value:
                  source: binance
                  type: crypto
                  symbol: ETH/USDT
                  side: open
                  direction: long
                  price: 3450.5
                  quantity: 1.5
                  signal_time: 1716500000000
                  client_signal_id: abc-123
                  metadata: { tp: 3600, sl: 3300, note: breakout }
      responses:
        '201':
          description: Signal stored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSignalResponse'
              example: { id: "019e599f744351937aae73ac9fb20f85", received_at: 1716500000123, deduped: false }
        '200':
          description: Duplicate of an earlier signal with the same `client_signal_id`; not stored again
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSignalResponse'
              example: { id: "019e599f744351937aae73ac9fb20f85", deduped: true }
        '400':
          description: Malformed JSON body
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              example: { error: bad_request, message: request body must be valid JSON }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              example: { error: unauthorized, message: invalid api key }
        '422':
          description: Validation failed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              example:
                error: validation_failed
                fields:
                  side: "must be one of: open, close"
  /healthz:
    get:
      tags: [health]
      summary: Liveness probe
      operationId: healthz
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, const: ok }
                required: [status]
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "API key as `Authorization: Bearer <key>`"
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  schemas:
    CreateSignalRequest:
      type: object
      required: [source, type, symbol, side]
      properties:
        source:
          type: string
          enum: [polymarket, binance, ibkr]
        type:
          type: string
          enum: [prediction_market, crypto, stock]
        symbol:
          type: string
          examples: ["BTC 15min", "ETH/USDT", "TSLA"]
        side:
          type: string
          enum: [open, close]
        direction:
          type: [string, "null"]
          enum: [long, short, buy, sell, null]
          description: Optional position direction
        price:
          type: [number, "null"]
        quantity:
          type: [number, "null"]
        signal_time:
          type: [integer, "null"]
          format: int64
          description: Trader-supplied event time, epoch milliseconds (UTC)
        client_signal_id:
          type: [string, "null"]
          description: Idempotency key; repeats with the same value are deduped per trader
        metadata:
          type: [object, "null"]
          additionalProperties: true
          description: Arbitrary extra fields stored as JSON
    CreateSignalResponse:
      type: object
      required: [id, deduped]
      properties:
        id:
          type: string
          description: Server-generated, time-sortable identifier
        received_at:
          type: integer
          format: int64
          description: Server receive time, epoch milliseconds (UTC). Omitted on dedupe.
        deduped:
          type: boolean
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Machine-readable error code
        message:
          type: string
        fields:
          type: object
          additionalProperties:
            type: string
          description: Per-field validation messages (only for validation_failed)
