{
  "openapi": "3.1.0",
  "info": {
    "title": "TerraByte API",
    "version": "1.0.0",
    "summary": "Natural-language search over satellite imagery.",
    "description": "Send a plain-English description of what you are looking for; receive ranked imagery matches with coordinates, source year, and a relevance score. Authenticate with an API key created in the portal dashboard (Settings \u2192 API keys), sent as a Bearer token.",
    "contact": {
      "name": "TerraByte AI",
      "email": "info@terrabyte.ai",
      "url": "https://terrabyte.ai/developers"
    }
  },
  "servers": [
    {
      "url": "https://portal.terrabyte.ai"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/v1/search": {
      "post": {
        "operationId": "search",
        "summary": "Search the imagery archive with natural language",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              },
              "example": {
                "text": "oil storage tanks",
                "top_k": 50
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked matches",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                },
                "example": {
                  "points": [
                    {
                      "lat": 32.7831,
                      "lon": -96.8067,
                      "score": 0.74,
                      "year": 2022
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request body failed validation",
            "content": {
              "application/json": {
                "example": {
                  "error": "invalid request body"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "example": {
                  "error": "missing Authorization: Bearer <api key>"
                }
              }
            }
          },
          "403": {
            "description": "The key's account email is not verified",
            "content": {
              "application/json": {
                "example": {
                  "error": "email_not_verified",
                  "detail": "Verify your email address to run searches."
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted or rate limited. Retry-After gives seconds until the window resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "error": "quota_exceeded",
                  "scope": "daily",
                  "limit": 20,
                  "used": 20,
                  "resets_at": "2026-09-05T00:00:00.000Z"
                }
              }
            }
          },
          "502": {
            "description": "Upstream search gateway error",
            "content": {
              "application/json": {
                "example": {
                  "error": "gateway error",
                  "status": 502
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "example": {
                  "error": "internal"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from the portal dashboard, sent as `Authorization: Bearer <key>`."
      }
    },
    "schemas": {
      "SearchRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "additionalProperties": false,
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "What you are looking for, in plain English."
          },
          "region": {
            "type": "string",
            "description": "Optional region hint."
          },
          "top_k": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200,
            "default": 100,
            "description": "How many matches to return."
          },
          "bbox": {
            "type": "array",
            "minItems": 4,
            "maxItems": 4,
            "items": {
              "type": "number"
            },
            "description": "[minLon, minLat, maxLon, maxLat]. When present it overrides any location parsed from the text."
          },
          "parse_nl": {
            "type": "boolean",
            "default": true,
            "description": "Rewrite the query and resolve locations to an area of interest, as the portal does. Set false to send text to the search backend verbatim."
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "points": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Point"
            }
          }
        }
      },
      "Point": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Relevance of the match to the query \u2014 a ranking signal, not a calibrated probability."
          },
          "year": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Acquisition year of the matched imagery."
          },
          "date": {
            "type": "string",
            "description": "Acquisition date, when known."
          },
          "scene_id": {
            "type": "string"
          },
          "cloud": {
            "type": "number"
          },
          "tile_url": {
            "type": "string"
          },
          "cell": {
            "type": "string"
          },
          "bbox": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 4,
            "maxItems": 4
          }
        }
      }
    }
  }
}
