{
  "openapi": "3.1.0",
  "info": {
    "title": "Layer400 Cloud API",
    "version": "1",
    "description": "Read-only received positions. Explicit account approval for each scope is required. API keys are server-side Bearer credentials. Live pages change between calls. History may be truncated; split windows. Coverage and retention are not guaranteed.",
    "contact": {
      "url": "https://layer400.com/contact/"
    }
  },
  "servers": [
    {
      "url": "https://layer400.com/business/api/v1"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/live/rid": {
      "get": {
        "operationId": "live_rid",
        "summary": "Current received drone Remote ID positions",
        "description": "Requires approved rid.live. Account-level quotas are shared by all keys. Polling only, not a stream.",
        "x-required-scope": "rid.live",
        "parameters": [
          {
            "name": "bbox",
            "in": "query",
            "required": true,
            "description": "west,south,east,north in WGS84 degrees; non-crossing with positive spans; maximum 30 degrees per side.",
            "schema": {
              "type": "string"
            },
            "example": "-2.3,50.7,-2.0,50.9"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 250
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Use previous next_cursor; omit on each new refresh. No immutable snapshot.",
            "schema": {
              "type": "string",
              "pattern": "^L4-[AD]-[0-9A-F]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Received positions; inspect next_cursor for live and truncated for history.",
            "headers": {
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-DailyLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Response"
                }
              }
            }
          },
          "400": {
            "description": "Check bounds, timestamps, cursor, duplicates and unknown parameters. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Check the Bearer header, key expiry and revocation in My Account. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Check approved layer, active grant, grant expiry and permitted lookback. Repeating the same call will not approve access. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Check the exact resource path. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Data endpoints accept GET only. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Respect Retry-After; all keys share one account allowance. Edge limits may also apply. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Use bounded exponential backoff with jitter; reduce dense query windows and contact support if persistent. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/live/adsb": {
      "get": {
        "operationId": "live_adsb",
        "summary": "Current received aircraft ADS-B positions",
        "description": "Requires approved adsb.live. Account-level quotas are shared by all keys. Polling only, not a stream.",
        "x-required-scope": "adsb.live",
        "parameters": [
          {
            "name": "bbox",
            "in": "query",
            "required": true,
            "description": "west,south,east,north in WGS84 degrees; non-crossing with positive spans; maximum 30 degrees per side.",
            "schema": {
              "type": "string"
            },
            "example": "-2.3,50.7,-2.0,50.9"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 250
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Use previous next_cursor; omit on each new refresh. No immutable snapshot.",
            "schema": {
              "type": "string",
              "pattern": "^L4-[AD]-[0-9A-F]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Received positions; inspect next_cursor for live and truncated for history.",
            "headers": {
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-DailyLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Response"
                }
              }
            }
          },
          "400": {
            "description": "Check bounds, timestamps, cursor, duplicates and unknown parameters. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Check the Bearer header, key expiry and revocation in My Account. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Check approved layer, active grant, grant expiry and permitted lookback. Repeating the same call will not approve access. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Check the exact resource path. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Data endpoints accept GET only. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Respect Retry-After; all keys share one account allowance. Edge limits may also apply. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Use bounded exponential backoff with jitter; reduce dense query windows and contact support if persistent. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/history/rid": {
      "get": {
        "operationId": "history_rid",
        "summary": "Retained drone position observations",
        "description": "Requires approved rid.history. Account-level quotas are shared by all keys. No cursor. Split any truncated time/area window.",
        "x-required-scope": "rid.history",
        "parameters": [
          {
            "name": "bbox",
            "in": "query",
            "required": true,
            "description": "west,south,east,north in WGS84 degrees; non-crossing with positive spans; maximum 5 degrees per side.",
            "schema": {
              "type": "string"
            },
            "example": "-2.3,50.7,-2.0,50.9"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 250
            }
          },
          {
            "name": "start",
            "in": "query",
            "required": true,
            "description": "RFC3339; [start,end), past window <=1 hour within 30 days AND approved lookback.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end",
            "in": "query",
            "required": true,
            "description": "RFC3339; [start,end), past window <=1 hour within 30 days AND approved lookback.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Received positions; inspect next_cursor for live and truncated for history.",
            "headers": {
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-DailyLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Response"
                }
              }
            }
          },
          "400": {
            "description": "Check bounds, timestamps, cursor, duplicates and unknown parameters. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Check the Bearer header, key expiry and revocation in My Account. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Check approved layer, active grant, grant expiry and permitted lookback. Repeating the same call will not approve access. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Check the exact resource path. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Data endpoints accept GET only. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Respect Retry-After; all keys share one account allowance. Edge limits may also apply. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Use bounded exponential backoff with jitter; reduce dense query windows and contact support if persistent. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/history/adsb": {
      "get": {
        "operationId": "history_adsb",
        "summary": "Retained aircraft position observations",
        "description": "Requires approved adsb.history. Account-level quotas are shared by all keys. No cursor. Split any truncated time/area window.",
        "x-required-scope": "adsb.history",
        "parameters": [
          {
            "name": "bbox",
            "in": "query",
            "required": true,
            "description": "west,south,east,north in WGS84 degrees; non-crossing with positive spans; maximum 5 degrees per side.",
            "schema": {
              "type": "string"
            },
            "example": "-2.3,50.7,-2.0,50.9"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 250
            }
          },
          {
            "name": "start",
            "in": "query",
            "required": true,
            "description": "RFC3339; [start,end), past window <=1 hour within 30 days AND approved lookback.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end",
            "in": "query",
            "required": true,
            "description": "RFC3339; [start,end), past window <=1 hour within 30 days AND approved lookback.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Received positions; inspect next_cursor for live and truncated for history.",
            "headers": {
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-DailyLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Response"
                }
              }
            }
          },
          "400": {
            "description": "Check bounds, timestamps, cursor, duplicates and unknown parameters. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Check the Bearer header, key expiry and revocation in My Account. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Check approved layer, active grant, grant expiry and permitted lookback. Repeating the same call will not approve access. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Check the exact resource path. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Data endpoints accept GET only. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Respect Retry-After; all keys share one account allowance. Edge limits may also apply. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Use bounded exponential backoff with jitter; reduce dense query windows and contact support if persistent. Edge-generated responses may be non-JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "When supplied, delay in seconds. Not present on every edge/503 response.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Named API key issued after explicit administrator approval. Not a website session or Pi credential."
      }
    },
    "schemas": {
      "Position": {
        "type": "object",
        "properties": {
          "public_track_id": {
            "description": "Rotating public reference: L4-D-… for drones, L4-A-… for aircraft. Not a permanent identity.",
            "type": "string",
            "pattern": "^L4-[AD]-[0-9A-F]{16}$"
          },
          "observed_at": {
            "description": "Time of the received position, with timezone. Use this to assess age.",
            "type": "string",
            "format": "date-time"
          },
          "longitude": {
            "description": "WGS84 decimal degrees, east positive.",
            "type": "number"
          },
          "latitude": {
            "description": "WGS84 decimal degrees, north positive.",
            "type": "number"
          },
          "altitude_m": {
            "description": "Reported/normalised altitude in metres. Its source may differ by track; v1 does not expose the altitude reference or uncertainty. Do not assume a surveyed height or use it for certified vertical separation.",
            "type": [
              "number",
              "null"
            ]
          },
          "height_agl_m": {
            "description": "Estimated metres above ground where available. Not interchangeable with altitude_m.",
            "type": [
              "number",
              "null"
            ]
          },
          "speed_mps": {
            "description": "Ground speed in metres per second. Multiply by 3.6 for km/h.",
            "type": [
              "number",
              "null"
            ]
          },
          "heading_deg": {
            "description": "Heading in degrees clockwise from north where available.",
            "type": [
              "number",
              "null"
            ]
          },
          "confidence": {
            "description": "Projection quality label, for example single, multi_station, stale or inconsistent. Accept new labels; this is not a safety assurance.",
            "type": [
              "string",
              "null"
            ]
          },
          "confidence_score": {
            "description": "Projection confidence score when available. Not a calibrated probability or detection guarantee.",
            "type": [
              "number",
              "null"
            ]
          },
          "receiver_count": {
            "description": "Contributing receiver count for this projected record, not the total network size.",
            "type": [
              "integer",
              "null"
            ]
          },
          "track_kind": {
            "description": "drone or adsb_aircraft.",
            "type": "string",
            "enum": [
              "drone",
              "adsb_aircraft"
            ]
          },
          "simulated": {
            "description": "Marks simulated data. Filter explicitly if you require non-simulated records.",
            "type": "boolean"
          },
          "test_broadcast": {
            "description": "Marks recognised TEST broadcasts. A false value is not proof that a record is operational traffic.",
            "type": "boolean"
          },
          "emergency_state": {
            "description": "Reported/projected state where available; unknown is not a confirmed all-clear. Live ADS-B v1 currently returns unknown.",
            "type": [
              "string",
              "null"
            ]
          },
          "stale_after": {
            "description": "Live projection expiry time. History records do not include this field.",
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "public_track_id",
          "observed_at",
          "longitude",
          "latitude",
          "altitude_m",
          "height_agl_m",
          "speed_mps",
          "heading_deg",
          "confidence",
          "confidence_score",
          "receiver_count",
          "track_kind",
          "simulated",
          "test_broadcast",
          "emergency_state"
        ],
        "additionalProperties": true
      },
      "Response": {
        "type": "object",
        "required": [
          "version",
          "request_id",
          "generated_at",
          "layer",
          "mode",
          "data",
          "next_cursor",
          "truncated"
        ],
        "properties": {
          "version": {
            "const": "1"
          },
          "request_id": {
            "type": "string"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "layer": {
            "enum": [
              "rid",
              "adsb"
            ]
          },
          "mode": {
            "enum": [
              "live",
              "history"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Position"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "truncated": {
            "type": "boolean"
          },
          "continuation": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "request_id": {
            "type": "string"
          }
        }
      }
    }
  }
}
