openapi: 3.1.0
info:
  title: l402-train Coordinator API
  version: 0.0.1
  description: |
    API specification for the l402-train coordinator service. Covers both
    training (gradient submission/validation) and autoresearch bounty endpoints.

    **Status: All endpoints implemented and tested (Phase 1 complete, 115 tests).
    No public server yet — coordinator runs on regtest. Poll /status.json for Phase 2 (public VPS deployment).**

    Authentication: L402 (HTTP 402 + Lightning invoice + macaroon).
    Gated endpoints require payment of a Lightning invoice before access.
    See https://l402-train.ai/research/l402-ecosystem.html for protocol details.
  contact:
    url: https://l402-train.ai
  license:
    name: MIT

servers:
  - url: https://coordinator.l402-train.ai
    description: Production coordinator (not yet live)
    x-status: implemented

paths:
  # ============================================================
  # Training endpoints
  # ============================================================

  /checkpoint:
    get:
      operationId: getCheckpoint
      summary: Download current model checkpoint
      description: |
        Returns the current model checkpoint for local training.
        L402-gated: peer pays a small access fee (~50 sats).
      tags: [training]
      x-status: implemented
      x-l402-gated: true
      x-l402-cost-sats: 50
      responses:
        '200':
          description: Model checkpoint binary
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '402':
          description: Payment required (L402 challenge)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/L402Challenge'

  /gradient:
    put:
      operationId: submitGradient
      summary: Submit compressed gradient for validation
      description: |
        Peer submits a SparseLoCo-compressed pseudo-gradient. A hold invoice
        locks payment at submission time. If the gradient improves the model
        (validated via forward-pass loss scoring on held-out batch), the hold
        invoice settles and the peer earns sats proportional to quality.
        If the gradient is harmful or useless, the hold invoice is cancelled
        and payment refunds immediately.
      tags: [training]
      x-status: implemented
      x-l402-gated: true
      x-payment-model: hold-invoice
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
              description: SparseLoCo-compressed pseudo-gradient (top-k sparse + 2-bit quantized)
      parameters:
        - name: X-Round-Number
          in: header
          required: true
          schema:
            type: integer
          description: Training round this gradient was computed for
        - name: X-Peer-Framework
          in: header
          required: false
          schema:
            type: string
            enum: [mlx, pytorch]
          description: Framework used (MLX for Apple Silicon, PyTorch for CUDA)
      responses:
        '200':
          description: Gradient accepted, hold invoice settled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GradientResult'
        '402':
          description: Payment required (L402 challenge with hold invoice)
        '422':
          description: Gradient rejected (validation failed, payment refunded)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GradientResult'

  /reward-schedule:
    get:
      operationId: getRewardSchedule
      summary: Current reward rates for training contributions
      description: |
        Public endpoint (no auth). Returns the current reward schedule:
        sats per unit of loss reduction, minimum quality threshold,
        and current training round.
      tags: [training]
      x-status: implemented
      x-l402-gated: false
      responses:
        '200':
          description: Current reward schedule
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RewardSchedule'

  # ============================================================
  # Autoresearch bounty endpoints
  # ============================================================

  /bounties:
    get:
      operationId: listBounties
      summary: List available bounties
      description: |
        Public endpoint (no auth). Returns all active bounties with their
        target description, metric, total sats available, and deadline.
      tags: [bounties]
      x-status: implemented
      x-l402-gated: false
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, completed, expired]
            default: active
        - name: min_reward
          in: query
          schema:
            type: integer
            minimum: 0
            maximum: 2100000000000000
          description: Minimum bounty reward in sats
      responses:
        '200':
          description: List of bounties
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BountySummary'

    post:
      operationId: createBounty
      summary: Create a new bounty
      description: |
        Sponsor creates a bounty by specifying target files, eval command,
        payment schedule, and held-out eval set hash (commit-reveal).
        Requires Lightning payment to fund the bounty pool.
      tags: [bounties]
      x-status: implemented
      x-l402-gated: true
      x-payment-model: direct
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BountyCreate'
      responses:
        '201':
          description: Bounty created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Bounty'
        '402':
          description: Payment required to fund bounty pool

  /bounty/{bountyId}:
    get:
      operationId: getBounty
      summary: Download bounty baseline
      description: |
        L402-gated. Returns the baseline code, public eval framework,
        and public eval dataset. The small access fee (~50 sats) covers
        bandwidth and acts as lightweight anti-spam.
      tags: [bounties]
      x-status: implemented
      x-l402-gated: true
      x-l402-cost-sats: 50
      parameters:
        - name: bountyId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Bounty baseline package
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BountyBaseline'
        '402':
          description: Payment required (L402 challenge)
        '404':
          description: Bounty not found

  /bounty/{bountyId}/submit:
    post:
      operationId: submitImprovement
      summary: Submit bounty improvement
      description: |
        Agent submits a code diff and claimed score. A hold invoice locks
        payment at submission time. The coordinator applies the diff to the
        baseline, runs evaluation on the held-out dataset (not the public
        one), and checks for gaming. If the improvement passes validation,
        the hold invoice settles proportional to improvement magnitude.
        80% settles immediately; 20% holdback released after 24-72 hour
        temporal stability check.
      tags: [bounties]
      x-status: implemented
      x-l402-gated: true
      x-payment-model: hold-invoice
      parameters:
        - name: bountyId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BountySubmission'
      responses:
        '200':
          description: Improvement accepted, hold invoice settled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BountyResult'
        '402':
          description: Payment required (hold invoice)
        '422':
          description: Improvement rejected (validation failed, payment refunded)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BountyResult'

  /bounty/{bountyId}/submissions:
    get:
      operationId: listSubmissions
      summary: List submissions for a bounty
      description: |
        Returns all submissions for a bounty with their validation results.
        Sponsors use this to track progress. Agents can see competing
        submissions (scores only, not diffs).
      tags: [bounties]
      x-status: implemented
      x-l402-gated: false
      parameters:
        - name: bountyId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: List of submissions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SubmissionSummary'

  # ============================================================
  # Status endpoints
  # ============================================================

  /status:
    get:
      operationId: getStatus
      summary: Coordinator status
      description: Public health check endpoint.
      tags: [status]
      x-status: implemented
      x-l402-gated: false
      responses:
        '200':
          description: Coordinator status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoordinatorStatus'

security:
  - L402: []

components:
  securitySchemes:
    L402:
      type: http
      scheme: bearer
      bearerFormat: "L402 <macaroon>:<preimage>"
      description: |
        L402 payment gating. Unauthenticated requests to gated endpoints receive
        HTTP 402 with a WWW-Authenticate header containing a Lightning invoice and
        macaroon. After paying the invoice, the client retries with:
        Authorization: L402 <base64-macaroon>:<hex-preimage>
        Verification is local: sha256(preimage) == payment_hash.

  schemas:
    L402Challenge:
      type: object
      properties:
        invoice:
          type: string
          description: BOLT11 Lightning invoice to pay
        macaroon:
          type: string
          description: Base64-encoded macaroon (becomes valid after invoice payment)
        amount_sats:
          type: integer
          description: Payment amount in satoshis

    GradientResult:
      type: object
      properties:
        accepted:
          type: boolean
        round:
          type: integer
        loss_before:
          type: number
        loss_after:
          type: number
        improvement:
          type: number
          description: Improvement magnitude (loss reduction)
        reward_sats:
          type: integer
          description: Sats earned (0 if rejected)
        reason:
          type: string
          description: Rejection reason (if rejected)

    RewardSchedule:
      type: object
      properties:
        current_round:
          type: integer
        sats_per_loss_unit:
          type: number
          description: Sats earned per unit of loss reduction
        minimum_improvement:
          type: number
          description: Minimum loss reduction to qualify for payment
        model:
          type: string
          description: Model being trained (e.g. "Qwen2.5-0.5B")

    BountySummary:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        metric:
          type: string
          description: What metric is being optimized
        total_sats:
          type: integer
          description: Total bounty pool in sats
        remaining_sats:
          type: integer
        deadline:
          type: string
          format: date-time
        submissions_count:
          type: integer
        best_improvement:
          type: number

    BountyCreate:
      type: object
      required: [title, metric, eval_command, total_sats, deadline, held_out_hash]
      properties:
        title:
          type: string
        description:
          type: string
        metric:
          type: string
          description: Human-readable description of the metric
        eval_command:
          type: string
          description: Command to run evaluation (must output numeric score)
        total_sats:
          type: integer
          description: Total bounty pool to fund
        deadline:
          type: string
          format: date-time
        held_out_hash:
          type: string
          description: SHA-256 hash of held-out eval set (commit-reveal)
        max_diff_bytes:
          type: integer
          description: Maximum diff size in bytes
        target_improvement:
          type: number
          description: Target metric improvement (100% payout threshold)
        baseline_tarball:
          type: string
          format: binary
          description: Baseline code + public eval dataset

    Bounty:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        metric:
          type: string
        total_sats:
          type: integer
        remaining_sats:
          type: integer
        deadline:
          type: string
          format: date-time
        held_out_hash:
          type: string
        created_at:
          type: string
          format: date-time

    BountyBaseline:
      type: object
      properties:
        bounty_id:
          type: string
        baseline_commit:
          type: string
          description: Git commit hash of baseline
        eval_command:
          type: string
        public_eval_dataset:
          type: string
          format: binary
        target_files:
          type: array
          items:
            type: string
          description: Files the agent is allowed to modify
        constraints:
          type: object
          properties:
            max_diff_bytes:
              type: integer
            forbidden_patterns:
              type: array
              items:
                type: string
            required_tests:
              type: string
              description: Command that must pass (e.g. test suite)

    BountySubmission:
      type: object
      required: [diff, claimed_score]
      properties:
        diff:
          type: string
          description: Unified diff of changes
        claimed_score:
          type: number
          description: Agent's claimed metric score on public eval set
        commit_hash:
          type: string
          description: Hash of the diff (for commit-reveal priority)
        notes:
          type: string
          description: Optional description of the approach

    BountyResult:
      type: object
      properties:
        accepted:
          type: boolean
        public_score:
          type: number
          description: Score on public eval set
        held_out_score:
          type: number
          description: Score on held-out eval set
        improvement:
          type: number
          description: Improvement magnitude on held-out set
        reward_sats:
          type: integer
          description: Sats earned (0 if rejected). 80% immediate, 20% holdback
        holdback_sats:
          type: integer
          description: Sats held back for temporal stability check
        holdback_release:
          type: string
          format: date-time
          description: When holdback will be evaluated
        reason:
          type: string
          description: Rejection reason (if rejected)

    SubmissionSummary:
      type: object
      properties:
        id:
          type: string
        submitted_at:
          type: string
          format: date-time
        held_out_score:
          type: number
        improvement:
          type: number
        reward_sats:
          type: integer
        status:
          type: string
          enum: [pending, accepted, rejected, holdback_pending, holdback_released]

    CoordinatorStatus:
      type: object
      properties:
        status:
          type: string
          enum: [online, degraded, offline]
        version:
          type: string
        current_round:
          type: integer
        active_peers:
          type: integer
        active_bounties:
          type: integer
        total_sats_paid:
          type: integer
