{
  "openapi": "3.1.0",
  "info": {
    "title": "Ephemeris API",
    "version": "1.0.0",
    "description": "Authenticated model discovery, balances, forecast execution, safe retries, costs, and usage history."
  },
  "servers": [
    {
      "url": "{baseUrl}",
      "variables": {
        "baseUrl": {
          "default": "http://localhost:3000"
        }
      }
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/api/v1/x402/forecast": {
      "post": {
        "operationId": "payForForecast",
        "summary": "Buy one explicit-model forecast with x402 v2 USDC",
        "description": "No Ephemeris account or API key required. Send an unsigned explicit-model ForecastRequest to receive a ten-minute payment quote. Use an x402 v2 wallet client to preserve the accepted requirements, including extra.ephemerisQuote, and retry the identical body with PAYMENT-SIGNATURE. Read the network and amount from the challenge; USDC has six decimals. This buys a forecast, not account credits. After submitting payment, retry the same signed request on timeout; do not create another authorization while the outcome is unresolved.",
        "security": [],
        "parameters": [
          {
            "name": "PAYMENT-SIGNATURE",
            "in": "header",
            "required": false,
            "description": "Base64 x402 v2 payment payload. Omit to obtain an unpaid quote. Preserve the original body and all accepted requirements.",
            "schema": {
              "type": "string",
              "maxLength": 16384
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ForecastRequest"
                  },
                  {
                    "type": "object",
                    "required": [
                      "model"
                    ],
                    "properties": {
                      "mode": {
                        "const": "explicit"
                      }
                    }
                  }
                ]
              },
              "example": {
                "mode": "explicit",
                "model": "chronos2",
                "series": [
                  {
                    "values": [
                      100.2,
                      101.4,
                      99.8,
                      103.1,
                      104.6,
                      102.2
                    ],
                    "freq": "H"
                  }
                ],
                "context_len": 256,
                "horizon": 16,
                "quantiles": [
                  0.1,
                  0.5,
                  0.9
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Settled payment and forecast, or cached result for the identical paid retry. No account balance is credited.",
            "headers": {
              "PAYMENT-RESPONSE": {
                "description": "Base64 x402 settlement receipt containing transaction and network information.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, unsupported mode, invalid price, or mismatched payment payload."
          },
          "402": {
            "description": "Unsigned request: x402 v2 challenge. Signed request: rejected payment authorization; inspect the error.",
            "headers": {
              "PAYMENT-REQUIRED": {
                "description": "Base64 x402 v2 challenge. Present on an unsigned quote response. Preserve all extra fields when signing.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "409": {
            "description": "Payment is processing, conflicts, or requires reconciliation. Do not submit a new payment."
          },
          "429": {
            "description": "Rate limited. Wait before retrying the same request."
          },
          "502": {
            "description": "Forecast or settlement failed. Inspect the error before paying again."
          },
          "503": {
            "description": "Payments disabled or service unavailable. If already submitted, retain and retry the same signed request."
          }
        }
      }
    },
    "/api/v1/models": {
      "get": {
        "operationId": "listModels",
        "summary": "List available models with prices and context limits",
        "responses": {
          "200": {
            "description": "Model catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Model"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/ProviderUnavailable"
          }
        }
      }
    },
    "/api/v1/balance": {
      "get": {
        "operationId": "getBalance",
        "summary": "Get the credits available to spend now",
        "responses": {
          "200": {
            "description": "Current balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/forecast": {
      "post": {
        "operationId": "runForecast",
        "summary": "Run and bill a forecast",
        "description": "Use the same Idempotency-Key when retrying an identical request. A completed retry returns the stored response without running or charging for the forecast again.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "A unique 8-128 character retry key. Keys are scoped to the authenticated user.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9._:-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForecastRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Forecast completed",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "Idempotency-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForecastResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Insufficient credits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientCredits"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency key conflict or request still processing",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "description": "Seconds before retrying an in-progress request",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ProviderUnavailable"
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "operationId": "listUsage",
        "summary": "List billed forecast request history",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsagePage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/mcp/x402": {
      "post": {
        "tags": [
          "Agent payments"
        ],
        "summary": "Accountless x402 MCP tools over Streamable HTTP",
        "security": [],
        "description": "Connect with an x402-capable MCP client. Free list_models discovers capabilities and prices. Paid forecast requires an explicit model. An unpaid tools/call returns isError: true with PaymentRequired in structuredContent and JSON content[0].text. Select one accepts entry and retry unchanged arguments with params._meta[\"x402/payment\"]. Success includes _meta[\"x402/payment-response\"]. Identical signed retries replay the stored result without another charge. This endpoint does not purchase account credits. Existing /api/mcp continues to require an API key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method"
                ],
                "properties": {
                  "jsonrpc": {
                    "const": "2.0"
                  },
                  "id": {
                    "oneOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                }
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/call",
                "params": {
                  "name": "forecast",
                  "arguments": {
                    "mode": "explicit",
                    "model": "chronos2",
                    "series": [
                      {
                        "values": [
                          100,
                          102,
                          101
                        ]
                      }
                    ],
                    "horizon": 16
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "MCP JSON-RPC result (JSON or SSE); inspect tool isError, structuredContent and payment receipt metadata."
          },
          "202": {
            "description": "MCP notification accepted."
          },
          "400": {
            "description": "Invalid JSON-RPC request or oversized body."
          },
          "429": {
            "description": "Admission rate limit; retry the same signed tool call later."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pc_live_...",
        "description": "Ephemeris application API key"
      }
    },
    "headers": {
      "RequestId": {
        "description": "Stable forecast request identifier used by usage logs and safe retries",
        "schema": {
          "type": "string",
          "pattern": "^req_[a-f0-9]{32}$"
        }
      },
      "IdempotencyReplayed": {
        "description": "True when a completed response was returned from the idempotency store",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ]
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate or capacity limit exceeded",
        "headers": {
          "Retry-After": {
            "description": "Seconds before retrying",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ProviderUnavailable": {
        "description": "Forecasting or pricing is temporarily unavailable",
        "headers": {
          "Retry-After": {
            "description": "Seconds before retrying",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "balance_mc",
          "active_holds_mc"
        ],
        "properties": {
          "balance_mc": {
            "type": "string",
            "pattern": "^-?[0-9]+$"
          },
          "active_holds_mc": {
            "type": "string",
            "pattern": "^[0-9]+$"
          }
        }
      },
      "Model": {
        "type": "object",
        "required": [
          "name",
          "price_per_kslot_mc",
          "billing_context_cap"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "price_per_kslot_mc": {
            "type": [
              "string",
              "null"
            ]
          },
          "billing_context_cap": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "ForecastSeries": {
        "type": "object",
        "required": [
          "values"
        ],
        "properties": {
          "values": {
            "oneOf": [
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "number"
                }
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "number"
                  }
                }
              }
            ]
          },
          "freq": {
            "type": "string",
            "minLength": 1,
            "description": "Optional per-series frequency. Mixed-frequency batches are split into compatible upstream calls and reassembled in request order."
          },
          "covariates": {
            "$ref": "#/components/schemas/SeriesCovariates"
          }
        },
        "additionalProperties": true
      },
      "SeriesCovariates": {
        "type": "object",
        "required": [
          "past"
        ],
        "properties": {
          "past": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            "description": "Named historical channels aligned to the series context length."
          },
          "future": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            "description": "Optional known-future channels aligned to the requested horizon. Every future channel must also have past history."
          }
        },
        "additionalProperties": false
      },
      "ForecastRequest": {
        "type": "object",
        "required": [
          "mode",
          "series"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "explicit",
              "ensemble",
              "route"
            ]
          },
          "model": {
            "type": "string"
          },
          "series": {
            "type": "array",
            "minItems": 1,
            "maxItems": 64,
            "items": {
              "$ref": "#/components/schemas/ForecastSeries"
            }
          },
          "context_len": {
            "type": "integer",
            "minimum": 1,
            "maximum": 16384
          },
          "horizon": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4096,
            "description": "Maximum 4096 steps. Slots x horizon x quantiles must not exceed 120000 output values; omitted or empty quantiles are budgeted as 21."
          },
          "quantiles": {
            "type": "array",
            "maxItems": 21,
            "items": {
              "type": "number",
              "exclusiveMinimum": 0,
              "exclusiveMaximum": 1
            }
          },
          "top_k": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 16,
            "description": "Optional ensemble-only limit on the number of models."
          },
          "combine": {
            "type": "string",
            "enum": [
              "mixture",
              "vincentize"
            ],
            "description": "Optional ensemble combination method."
          }
        },
        "additionalProperties": true
      },
      "ForecastResponse": {
        "type": "object",
        "required": [
          "meta"
        ],
        "properties": {
          "meta": {
            "type": "object",
            "required": [
              "gateway_request_id",
              "billing"
            ],
            "properties": {
              "gateway_request_id": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              },
              "models_used": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "upstream_request_ids": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Model request IDs when a mixed-frequency request is split into compatible groups."
              },
              "upstream_calls": {
                "type": "integer",
                "minimum": 1
              },
              "billing": {
                "type": "object",
                "required": [
                  "settled_mc",
                  "balance_mc"
                ],
                "properties": {
                  "settled_mc": {
                    "type": "string"
                  },
                  "balance_mc": {
                    "type": "string"
                  }
                }
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "InsufficientCredits": {
        "type": "object",
        "required": [
          "error",
          "balance_mc",
          "required_mc",
          "topup_url"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "balance_mc": {
            "type": "string"
          },
          "required_mc": {
            "type": "string"
          },
          "topup_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "UsageRow": {
        "type": "object",
        "required": [
          "requestId"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "paracastRequestId": {
            "type": [
              "string",
              "null"
            ]
          },
          "estimatedMc": {
            "type": [
              "string",
              "null"
            ]
          },
          "settledMc": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "UsagePage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageRow"
            }
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "pagination": {
            "type": "object",
            "required": [
              "limit",
              "offset",
              "returned",
              "has_more",
              "next_offset"
            ],
            "properties": {
              "limit": {
                "type": "integer"
              },
              "offset": {
                "type": "integer"
              },
              "returned": {
                "type": "integer"
              },
              "has_more": {
                "type": "boolean"
              },
              "next_offset": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          }
        }
      }
    }
  }
}
