{
  "openapi": "3.0.3",
  "info": {
    "title": "RentGPUPower General Compute API",
    "version": "1",
    "description": "Serverless GPU jobs and per-second instances across the host pool. Authenticate with an account key (`Authorization: Bearer gpuapi_…`, create one on /developers). Errors are JSON `{error, code}`. Every rate-limited response carries X-RateLimit-Limit / -Remaining / -Reset; a 429 and a 409 no_capacity carry Retry-After. Read-only keys answer every GET and get 403 key_read_only on anything else."
  },
  "servers": [
    {
      "url": "https://rentgpupower.com/api/v1/compute"
    }
  ],
  "components": {
    "securitySchemes": {
      "accountKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "gpuapi_…"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "retry_after_s": {
            "type": "integer"
          }
        },
        "required": [
          "error",
          "code"
        ]
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "attempt": {
            "type": "integer",
            "description": "1, or 2 when re-run after a host failed the first attempt"
          },
          "retried_because": {
            "type": "string",
            "description": "Present on a re-run: why the first machine was dropped"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "scheduling",
              "running",
              "succeeded",
              "failed",
              "cancelled"
            ]
          },
          "queued_ahead": {
            "type": "integer",
            "description": "Only while queued: jobs ahead in the FIFO line."
          },
          "queue_deadline": {
            "type": "integer",
            "description": "Only while queued: ms epoch after which waiting fails honestly."
          },
          "command": {
            "type": "string"
          },
          "image": {
            "type": "string",
            "nullable": true
          },
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "timeout_s": {
            "type": "integer"
          },
          "gpu_model": {
            "type": "string",
            "nullable": true
          },
          "listing_id": {
            "type": "integer",
            "nullable": true
          },
          "array_id": {
            "type": "string",
            "nullable": true
          },
          "webhook": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "pending",
                  "delivered",
                  "failed"
                ]
              },
              "attempts": {
                "type": "integer"
              },
              "last_status": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "hold_cents": {
            "type": "integer"
          },
          "billed_cents": {
            "type": "integer",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "exit_code": {
            "type": "integer",
            "nullable": true
          },
          "output": {
            "type": "string"
          },
          "created_at": {
            "type": "integer"
          },
          "started_at": {
            "type": "integer",
            "nullable": true
          },
          "finished_at": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "JobRequest": {
        "type": "object",
        "required": [
          "command"
        ],
        "properties": {
          "command": {
            "type": "string",
            "maxLength": 4000
          },
          "timeout_s": {
            "type": "integer",
            "minimum": 5,
            "maximum": 3600,
            "default": 300
          },
          "image": {
            "type": "string",
            "description": "Public registry reference; [A-Za-z0-9_./:@-], max 200."
          },
          "template_id": {
            "type": "string"
          },
          "gpu_model": {
            "type": "string"
          },
          "min_vram_gb": {
            "type": "integer"
          },
          "region": {
            "type": "string"
          },
          "max_price_cents_hour": {
            "type": "integer"
          },
          "min_gpu_count": {
            "type": "integer",
            "description": "Rigs: schedule onto whole-machine listings with at least N devices."
          },
          "replicas": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Job array: N independent pool jobs, reported per item. Idempotency-Key not allowed with replicas."
          },
          "queue_s": {
            "type": "integer",
            "minimum": 1,
            "maximum": 600,
            "description": "Hold the job server-side up to N seconds when the pool is full (202 queued, FIFO, free until scheduled)."
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "POSTed the finished job, signed (X-GPURENT-Signature: t=…,v1=HMAC-SHA256(webhook_secret, t.body)); 3 delivery attempts in total (retries after 10 s, then 60 s by default); private/internal addresses refused. Output in the payload is capped at 64 KB (output_truncated, output_bytes) — the full output stays on GET /jobs/{id}."
          }
        }
      },
      "Instance": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "gpu_model": {
            "type": "string"
          },
          "vram_gb": {
            "type": "integer",
            "nullable": true
          },
          "region": {
            "type": "string",
            "nullable": true
          },
          "price_cents_hour": {
            "type": "integer"
          },
          "duration_s": {
            "type": "integer"
          },
          "started_at": {
            "type": "integer",
            "nullable": true
          },
          "expires_at": {
            "type": "integer"
          },
          "hold_cents": {
            "type": "integer"
          },
          "billed_cents": {
            "type": "integer",
            "nullable": true
          },
          "cost_so_far_cents": {
            "type": "integer"
          },
          "refunded_cents": {
            "type": "integer"
          },
          "uptime": {
            "type": "object"
          },
          "template_id": {
            "type": "string",
            "nullable": true
          },
          "served_model": {
            "type": "string",
            "nullable": true
          },
          "service": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "command"
        ],
        "description": "One request, many tasks: the command runs once per task, on whichever pool machines are free, max_parallel at a time. Each task sees GPURENT_BATCH_ID, GPURENT_TASK_INDEX (0…task_count-1), GPURENT_TASK_COUNT, GPURENT_TASK_ATTEMPT and, with inputs, GPURENT_TASK_INPUT. Each task is billed like a job (per second, its own hold).",
        "properties": {
          "command": {
            "type": "string",
            "maxLength": 4000
          },
          "tasks": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000,
            "description": "How many tasks (or give inputs, one per task)."
          },
          "inputs": {
            "type": "array",
            "maxItems": 100000,
            "items": {},
            "description": "One entry per task, handed to it as GPURENT_TASK_INPUT — a string as it is, anything else as its JSON; at most 4096 characters each. Put big data at a URL and pass the URL."
          },
          "max_parallel": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "Tasks running at once; min(tasks, 100) when omitted."
          },
          "max_attempts": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "default": 2,
            "description": "Tries per task; a failed attempt is tried again on another machine."
          },
          "budget_cents": {
            "type": "integer",
            "minimum": 1,
            "description": "No new task starts once what the batch has spent and holds reaches this."
          },
          "deadline_s": {
            "type": "integer",
            "minimum": 60,
            "maximum": 604800,
            "default": 86400,
            "description": "Tasks not started by then fail; the batch then finishes."
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "timeout_s": {
            "type": "integer",
            "minimum": 5,
            "maximum": 3600,
            "default": 300,
            "description": "Per task."
          },
          "image": {
            "type": "string"
          },
          "template_id": {
            "type": "string"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "POSTed {event:\"batch.finished\", batch} when the batch is done or cancelled, signed like job webhooks."
          },
          "gpu_model": {
            "type": "string"
          },
          "min_vram_gb": {
            "type": "integer"
          },
          "region": {
            "type": "string"
          },
          "max_price_cents_hour": {
            "type": "integer"
          },
          "min_gpu_count": {
            "type": "integer"
          }
        }
      },
      "Batch": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "bat_…"
          },
          "object": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "done",
              "cancelled"
            ]
          },
          "stop_reason": {
            "type": "string",
            "nullable": true,
            "description": "deadline / budget / credits / cancelled"
          },
          "command": {
            "type": "string"
          },
          "task_count": {
            "type": "integer"
          },
          "tasks": {
            "type": "object",
            "properties": {
              "pending": {
                "type": "integer"
              },
              "queued": {
                "type": "integer"
              },
              "running": {
                "type": "integer"
              },
              "succeeded": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "cancelled": {
                "type": "integer"
              }
            }
          },
          "progress": {
            "type": "number",
            "description": "0…1, finished tasks / task_count"
          },
          "max_parallel": {
            "type": "integer"
          },
          "max_attempts": {
            "type": "integer"
          },
          "budget_cents": {
            "type": "integer",
            "nullable": true
          },
          "spent_cents": {
            "type": "integer"
          },
          "held_cents": {
            "type": "integer"
          },
          "deadline_at": {
            "type": "integer"
          },
          "created_at": {
            "type": "integer"
          },
          "finished_at": {
            "type": "integer",
            "nullable": true
          },
          "webhook": {
            "type": "object"
          }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "type": {
                  "type": "string"
                },
                "amount_cents": {
                  "type": "integer"
                },
                "note": {
                  "type": "string"
                },
                "rental_id": {
                  "type": "integer",
                  "nullable": true
                },
                "created_at": {
                  "type": "integer"
                }
              }
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "inference": {
            "type": "object"
          },
          "days": {
            "type": "array",
            "description": "Seven UTC-day buckets, oldest first, ending today",
            "items": {
              "type": "object",
              "properties": {
                "day_start": {
                  "type": "integer"
                },
                "jobs": {
                  "type": "integer"
                },
                "instances": {
                  "type": "integer"
                },
                "spent_cents": {
                  "type": "integer",
                  "description": "Everything API work cost that day, embeddings included"
                },
                "gpu_seconds": {
                  "type": "integer"
                },
                "embeddings_cents": {
                  "type": "integer",
                  "description": "Of spent_cents: the embeddings API"
                }
              }
            }
          }
        }
      },
      "Capacity": {
        "type": "object",
        "properties": {
          "pool": {
            "type": "object",
            "properties": {
              "gpus_online": {
                "type": "integer"
              },
              "available_now": {
                "type": "integer"
              },
              "retry_hint_s": {
                "type": "integer"
              },
              "queued_jobs": {
                "type": "integer"
              },
              "models": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "gpu_model": {
                      "type": "string"
                    },
                    "online": {
                      "type": "integer"
                    },
                    "available": {
                      "type": "integer"
                    },
                    "floor_cents_hour": {
                      "type": "integer",
                      "nullable": true
                    },
                    "max_vram_gb": {
                      "type": "integer",
                      "nullable": true
                    },
                    "max_gpu_count": {
                      "type": "integer"
                    },
                    "regions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "your": {
            "type": "object",
            "properties": {
              "balance_cents": {
                "type": "integer"
              },
              "jobs_running": {
                "type": "integer"
              },
              "jobs_queued": {
                "type": "integer"
              },
              "instances_running": {
                "type": "integer"
              },
              "rate_limit": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "integer"
                  },
                  "remaining": {
                    "type": "integer"
                  },
                  "reset": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "NoCredits": {
        "description": "Insufficient credits or spend limit",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NoCapacity": {
        "description": "No idle machine matches; Retry-After says when to come back",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Invalid": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests; Retry-After says when",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "accountKey": []
    }
  ],
  "paths": {
    "/gpus": {
      "get": {
        "summary": "Public pool catalog with floor prices (no auth)",
        "security": [],
        "parameters": [
          {
            "name": "gpu_model",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_vram_gb",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "region",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "max_price_cents_hour",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "gpus, cheapest per model, available_now"
          }
        }
      }
    },
    "/capacity": {
      "get": {
        "summary": "What can I run right now? Same filters as POST /jobs, the scheduler's own view",
        "parameters": [
          {
            "name": "gpu_model",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_vram_gb",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "region",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "max_price_cents_hour",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "min_gpu_count",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "template_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Capacity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capacity"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Account, key info, balance, and the webhook signing secret (null for read-only keys)",
        "responses": {
          "200": {
            "description": "account, key, balance_cents, webhook_secret"
          }
        }
      }
    },
    "/balance": {
      "get": {
        "summary": "Credit balance",
        "responses": {
          "200": {
            "description": "balance_cents, currency"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "summary": "Ledger lines, inference totals and a 7-day rollup for this key's account",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "usage",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                }
              }
            }
          }
        }
      }
    },
    "/topups": {
      "post": {
        "summary": "Start buying credits (card or Solana)",
        "responses": {
          "201": {
            "description": "topup order"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/topups/{id}": {
      "get": {
        "summary": "Top-up order status",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "order"
          },
          "404": {
            "description": "not found"
          }
        }
      }
    },
    "/topups/{id}/solana-intent": {
      "post": {
        "summary": "Quote lamports + reference for paying this order in SOL",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "intent"
          }
        }
      }
    },
    "/topups/{id}/stripe-intent": {
      "post": {
        "summary": "Create the card payment intent for this order",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "intent"
          }
        }
      }
    },
    "/topups/{id}/confirm": {
      "post": {
        "summary": "Confirm the payment landed; credits are added",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "credited"
          }
        }
      }
    },
    "/jobs": {
      "post": {
        "summary": "Run a command on the cheapest idle GPU (or queue for one)",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "idempotent replay"
          },
          "201": {
            "description": "scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job": {
                      "$ref": "#/components/schemas/Job"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "queued (queue_s) — poll or stream the same job"
          },
          "402": {
            "$ref": "#/components/responses/NoCredits"
          },
          "409": {
            "$ref": "#/components/responses/NoCapacity"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "summary": "List jobs, or aggregate one array",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          },
          {
            "name": "array",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "array_id: returns total, by_status, billed_cents_total, done, jobs"
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Cursor: only jobs with id < before (use the previous reply's next_before)."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Only jobs created at or after this ms epoch."
          }
        ],
        "responses": {
          "200": {
            "description": "jobs, has_more, next_before"
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "summary": "One job with output",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "job",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job": {
                      "$ref": "#/components/schemas/Job"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not found"
          }
        }
      },
      "delete": {
        "summary": "Cancel (queued jobs leave the line free of charge; running ones settle per second)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "cancelled / already finished"
          }
        }
      }
    },
    "/jobs/{id}/stream": {
      "get": {
        "summary": "Server-sent events: output deltas, status changes, done — stays open through a queue wait",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "text/event-stream: events output {text}, status {status}, done {status, exit_code, billed_cents}"
          }
        }
      }
    },
    "/instances": {
      "post": {
        "summary": "Reserve a GPU by the second; returns a per-instance rental_api_key for the data plane",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "duration_s": {
                    "type": "integer",
                    "description": "Seconds to reserve; 3600 when omitted"
                  },
                  "listing_id": {
                    "type": "integer",
                    "description": "Pin one specific pool machine"
                  },
                  "template_id": {
                    "type": "string"
                  },
                  "image": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Your own public Docker image instead of a template (it needs sh); not with template_id"
                  },
                  "onstart": {
                    "type": "string",
                    "maxLength": 4000,
                    "description": "A shell command run once, first, when the instance starts"
                  },
                  "model": {
                    "type": "string"
                  },
                  "gpu_model": {
                    "type": "string"
                  },
                  "min_vram_gb": {
                    "type": "integer"
                  },
                  "region": {
                    "type": "string"
                  },
                  "max_price_cents_hour": {
                    "type": "integer"
                  },
                  "min_gpu_count": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "instance + rental_api_key"
          },
          "402": {
            "$ref": "#/components/responses/NoCredits"
          },
          "409": {
            "$ref": "#/components/responses/NoCapacity"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      },
      "get": {
        "summary": "Your instances (a read-only key sees each service.url as null — the URL is a credential)",
        "responses": {
          "200": {
            "description": "instances"
          }
        }
      }
    },
    "/instances/{id}": {
      "get": {
        "summary": "One instance",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "instance, rental_api_key — both rental_api_key and instance.service.url are null for a read-only key, since each one controls the machine"
          },
          "404": {
            "description": "not found"
          }
        }
      },
      "delete": {
        "summary": "Destroy — settles per second, unused reservation refunded",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok, billed_cents, refunded_cents"
          }
        }
      }
    },
    "/instances/{id}/ready": {
      "get": {
        "summary": "Is the model server answering yet? (a real request, not a guess)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "exists, ready, status, model"
          }
        }
      }
    },
    "/instances/{id}/extend": {
      "post": {
        "summary": "Add seconds to a running instance",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "duration_s"
                ],
                "properties": {
                  "duration_s": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "extended"
          },
          "402": {
            "$ref": "#/components/responses/NoCredits"
          }
        }
      }
    },
    "/ssh-keys": {
      "get": {
        "summary": "The SSH public keys on this account (every rental lets them in once SSH is on)",
        "responses": {
          "200": {
            "description": "keys [{id, name, type, fingerprint, created_at}], max"
          }
        }
      },
      "post": {
        "summary": "Add an SSH public key (idempotent by fingerprint)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_key"
                ],
                "properties": {
                  "public_key": {
                    "type": "string",
                    "description": "One line of a .pub file: ssh-ed25519 or ssh-rsa (2048+), ecdsa, sk-…"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "already there"
          },
          "201": {
            "description": "added"
          },
          "409": {
            "description": "too_many_keys (20)"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/ssh-keys/{id}": {
      "delete": {
        "summary": "Remove an SSH key (rentals keep it until SSH is turned on again)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok, keys"
          }
        }
      }
    },
    "/ssh/{id}": {
      "post": {
        "summary": "Turn SSH on in a rental you hold: sshd in its container (installed on first use), your keys only; again after adding a key. Connect with `gpurent ssh <id>` — the bytes ride the tunnel at /tunnel/<id>?port=22 (WebSocket, your key in Authorization)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "public_key": {
                    "type": "string",
                    "description": "Added to the account first, if given"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{rental_id, state: setting_up|ready|failed|off, job_id, error?, connect}"
          },
          "404": {
            "description": "not your rental"
          },
          "410": {
            "description": "rental_ended"
          },
          "422": {
            "description": "no_keys / invalid_key / not_a_machine"
          }
        }
      },
      "get": {
        "summary": "Where SSH stands in that rental (setting_up, ready, failed with the reason)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "state"
          }
        }
      }
    },
    "/batches": {
      "post": {
        "summary": "Start a batch: up to 100,000 tasks over the whole pool, max_parallel at a time",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "batch": {
                      "$ref": "#/components/schemas/Batch"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/NoCredits"
          },
          "409": {
            "$ref": "#/components/responses/NoCapacity"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "description": "too_many_batches (20 running per account) / rate limited"
          }
        }
      },
      "get": {
        "summary": "Your batches, newest first",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "batches, has_more, next_before"
          }
        }
      }
    },
    "/batches/{id}": {
      "get": {
        "summary": "One batch: task counts, progress, what it has spent",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "batch": {
                      "$ref": "#/components/schemas/Batch"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not found"
          }
        }
      }
    },
    "/batches/{id}/tasks": {
      "get": {
        "summary": "Its tasks by index — status, attempts, input, exit code, error, cost; ?output=1 adds each one's output (64 KB each, 100 a page)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "task index to start after"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 1000
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "output",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "tasks, has_more, next_after"
          },
          "404": {
            "description": "not found"
          }
        }
      }
    },
    "/batches/{id}/cancel": {
      "post": {
        "summary": "Stop it: waiting tasks never start, running ones stop and settle per second",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "batch": {
                      "$ref": "#/components/schemas/Batch"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/models": {
      "get": {
        "summary": "Models your instances are serving, models idle AI devices serve (device: true — a chat call for one rents that device and is answered at once), plus ones you could start",
        "responses": {
          "200": {
            "description": "OpenAI-style model list"
          }
        }
      }
    },
    "/embeddings": {
      "post": {
        "summary": "Text embeddings (all-MiniLM-L6-v2, 384 dimensions) in OpenAI's request and response shape, computed by phones with /earn open: each piece of the request goes to two phones of different accounts on different internet connections and is delivered only when they agree. Your texts are processed on other people's phones — do not send anything confidential. Billed per token (one token per 4 characters) only for verified answers, from credits you bought or earned (free and phone credit cannot pay). Send an Idempotency-Key to retry safely: the same key answers the same vectors for a day (Idempotent-Replayed: true) without charging again.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "input"
                ],
                "properties": {
                  "input": {
                    "oneOf": [
                      {
                        "type": "string",
                        "maxLength": 2000
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 2000
                        },
                        "maxItems": 64
                      }
                    ]
                  },
                  "model": {
                    "type": "string",
                    "enum": [
                      "all-MiniLM-L6-v2",
                      "sentence-transformers/all-MiniLM-L6-v2"
                    ]
                  },
                  "encoding_format": {
                    "type": "string",
                    "enum": [
                      "float",
                      "base64"
                    ]
                  },
                  "dimensions": {
                    "type": "integer",
                    "enum": [
                      384
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{object:'list', model, data:[{object:'embedding', index, embedding}], usage:{prompt_tokens, total_tokens}} — embedding is 384 floats, or base64 little-endian float32"
          },
          "402": {
            "description": "insufficient_credits (free and phone credit cannot pay) / spend_limit_exceeded / spend_cap"
          },
          "404": {
            "description": "not_offered — this server does not run the phone pool"
          },
          "409": {
            "description": "request_in_progress — a request with this Idempotency-Key is still being worked on (Retry-After)"
          },
          "422": {
            "description": "invalid_input / too_many_inputs / input_too_long / too_many_tokens / tokens_not_supported / unknown_model / unsupported_encoding / unsupported_dimensions / idempotency_key_reused"
          },
          "429": {
            "description": "too_many_waiting — at most 8 requests per account wait at once (Retry-After)"
          },
          "503": {
            "description": "no_capacity — too few phones online (phones_online, phones_needed, Retry-After); timeout / unverified — not delivered, nothing charged, retry; paused / busy / shutting_down (Retry-After)"
          }
        }
      }
    },
    "/chat/completions": {
      "post": {
        "summary": "OpenAI-compatible chat completions against your instance (streams). No instance serving that model yet? A vetted vLLM model or any Ollama tag is started for you on your credits: 503 model_loading with instance_id, eta_s and Retry-After until it answers. A model an idle AI device serves (GET /models, device: true) rents that device for you instead — 30 min reserved, per second, on your credits — and is answered at once.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "model": {
                    "type": "string"
                  },
                  "messages": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "stream": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "completion or SSE stream",
            "headers": {
              "X-RentGPUPower-Instance": {
                "description": "Set when this call rented an AI device for you: the instance id (GET /instances/{id}; DELETE to stop it). Later calls for the same model reuse it.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "402": {
            "description": "insufficient_credits / spend_limit_exceeded / spend_cap — nothing was started"
          },
          "409": {
            "description": "no_capacity — no idle machine can run it right now (alternatives listed)"
          },
          "503": {
            "description": "model_loading: your instance is starting or downloading the model (starting, stage, instance_id, eta_s, ready_url; Retry-After header) — retry. pull_failed: the download failed (reason in message). model_not_loaded: nothing here can start that model; available and startable are listed with a fix."
          }
        }
      }
    }
  }
}