ORBITAL GRID API
Una sola API sobre los satélites de observación terrestre del mundo. Describís qué necesitás — área, fechas, tipo de dato, resolución, tolerancia a nubes — y el motor de ruteo elige el mejor satélite, consulta el catálogo real y hace auto-switch cuando hace falta (incluido óptico→SAR cuando las nubes tapan la vista).
Base URL: …
Qué es ORBITAL GRID
- Un registry de satélites con tres estados honestos:
live(datos reales ya),requires_credential(conector listo, se activa al configurar la key) ypending_agreement(comercial, a la espera de convenio). - Un motor de ruteo que puntúa cada satélite capaz y explica por qué eligió el que eligió, con la traza completa de auto-switch.
- Una capa de inteligencia que convierte una pregunta de negocio en español en parámetros técnicos.
- Un SKU asistido por analista que entrega escenas, índices y evidencia técnica con AOI, fuente, fecha, QA, cobertura, linaje y limitaciones.
Quickstart
1. Solicitá tu API key: contanos quién sos y para qué la necesitás en Solicitar API Key, y el equipo de Orbital Grid te la hace llegar.
2. Hacé tu primera llamada:
curl -s -X POST {{BASE}}/v1/intent \
-H "Content-Type: application/json" \
-H "X-API-Key: TU_API_KEY" \
-d '{"query":"serie NDVI y NDMI de mi AOI en Pergamino en el último mes"}'
import httpx
r = httpx.post(
"{{BASE}}/v1/intent",
headers={"X-API-Key": "TU_API_KEY"},
json={"query": "serie NDVI y NDMI de mi AOI en Pergamino en el último mes"},
timeout=60,
)
data = r.json()
print(data["selected_satellite"], "→", data["explanation"])
const r = await fetch("{{BASE}}/v1/intent", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "TU_API_KEY" },
body: JSON.stringify({ query: "serie NDVI y NDMI de mi AOI en Pergamino en el último mes" }),
});
const data = await r.json();
console.log(data.selected_satellite, data.explanation);
3. Descargá el contrato versionable en /openapi.json. La UI interactiva permanece deshabilitada en producción.
Autenticación
Todas las direcciones de datos piden el header X-API-Key. Pedí tu key en Solicitar API Key: se muestra una única vez y se guarda hasheada del lado del servidor.
Cada key tiene un rate limit por minuto según su plan, y su uso queda medido por endpoint y por día — lo consultás en /v1/account/usage.
POST /v1/portal/keys/revoke y emití una nueva.Salud de la plataforma
Estado del servicio, conteos del registry y alcanzabilidad de cada catálogo upstream (probado de verdad, con caché corto).
curl -s {{BASE}}/health
import httpx
print(httpx.get("{{BASE}}/health", timeout=30).json())
const h = await (await fetch("{{BASE}}/health")).json();
console.log(h.status, h.catalogs);
El listado de satélites
Todos los satélites registrados con sus capacidades y estado de acceso.
| Query param | Valores | Descripción |
|---|---|---|
data_type | optical · multispectral · sar · thermal · atmospheric · elevation | Filtra por tipo de dato. |
access | live · requires_credential · pending_agreement | Filtra por estado de acceso. |
cost_tier | free · commercial | Filtra por costo del dato. |
curl -s "{{BASE}}/v1/satellites?data_type=sar&access=live"
import httpx
sats = httpx.get("{{BASE}}/v1/satellites",
params={"data_type": "sar", "access": "live"}, timeout=30).json()
print(sats["count"], "satélites SAR live")
const s = await (await fetch("{{BASE}}/v1/satellites?data_type=sar&access=live")).json();
console.log(s.count, "satélites SAR live");
Búsqueda unificada de imágenes
El corazón de la plataforma: una búsqueda estructurada sobre todos los satélites. El motor puntúa candidatos, consulta el mejor catálogo y auto-switchea si viene vacío.
| Campo | Tipo | Descripción |
|---|---|---|
bbox | [w,s,e,n] | AOI como bounding box WGS84 (esto o geometry). |
geometry | GeoJSON | AOI como geometría GeoJSON. |
datetime_from / datetime_to | ISO-8601 | Rango de fechas. |
data_type | enum | optical · multispectral · sar · thermal · atmospheric · elevation. |
max_resolution_m | float | GSD más grueso aceptable. |
cloud_cover_max | 0–100 | Máximo % de nubes (óptico). |
priority | enum | recency · resolution · cost. |
allow_sar_fallback | bool | Si el óptico no encuentra nada bajo el tope de nubes, cambia a SAR (default true). |
limit | int | Máx. escenas (1–50). |
off_nadir_max | 0–90 | Ángulo máximo de visión off-nadir aceptable. Una escena que no publica view:off_nadir no se descarta: se conserva con una advertencia. |
level | enum | Nivel de procesamiento (vocabulario propio): raw · calibrated · surface · composite · model. Una escena cuyo nivel no se puede determinar no se descarta: se conserva con una advertencia. |
curl -s -X POST {{BASE}}/v1/imagery/search \
-H "Content-Type: application/json" -H "X-API-Key: TU_API_KEY" \
-d '{"bbox":[-60.70,-34.00,-60.50,-33.80],
"datetime_from":"2026-04-01","datetime_to":"2026-07-06",
"data_type":"optical","max_resolution_m":30,
"cloud_cover_max":20,"priority":"resolution","limit":3}'
import httpx
r = httpx.post("{{BASE}}/v1/imagery/search",
headers={"X-API-Key": "TU_API_KEY"},
json={"bbox": [-60.70, -34.00, -60.50, -33.80],
"datetime_from": "2026-04-01", "datetime_to": "2026-07-06",
"data_type": "optical", "max_resolution_m": 30,
"cloud_cover_max": 20, "priority": "resolution", "limit": 3},
timeout=120)
res = r.json()
print(res["selected_satellite"], res["selected_reason"])
for sc in res["scenes"]:
print(" ", sc["satellite"], sc["datetime"], sc.get("cloud_cover"))
const r = await fetch("{{BASE}}/v1/imagery/search", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "TU_API_KEY" },
body: JSON.stringify({
bbox: [-60.70, -34.00, -60.50, -33.80],
datetime_from: "2026-04-01", datetime_to: "2026-07-06",
data_type: "optical", max_resolution_m: 30,
cloud_cover_max: 20, priority: "resolution", limit: 3,
}),
});
const res = await r.json();
console.log(res.selected_satellite, res.switched, res.switch_trace);
La respuesta incluye selected_satellite, selected_reason, switched + switch_trace (la historia del auto-switch), scenes normalizadas con links de preview/descarga, candidates_considered y catalogs_status.
scene puede traer geometry (footprint GeoJSON real, no solo el bbox), license, og_level (raw·calibrated·surface·composite·model), proj_code (ej. EPSG:32720), view (off_nadir, ángulos solares, incidencia), eo_bands, sar (banda, polarizaciones, modo), quality_mask (scl·qa_pixel·fmask·none) y contract_gaps (los campos MUST que el upstream no publica). Todos son opcionales: si el ítem upstream no los expone, quedan en null y la respuesta no cambia. Además, la respuesta puede incluir contract_report con rejected, rejection_causes y gap_counts cuando el gate de contrato descartó escenas.Preguntá en lenguaje natural
Mandá una pregunta de negocio en español; ORBITAL GRID infiere AOI, tipo de dato, índices (NDVI/NDWI/NBR/SAR), ventana temporal, tolerancia a nubes y prioridad, rutea con el mismo motor y explica la decisión en español.
curl -s -X POST {{BASE}}/v1/intent \
-H "Content-Type: application/json" -H "X-API-Key: TU_API_KEY" \
-d '{"query":"buscar escenas comparables y señales candidatas de agua nueva en Pehuajó"}'
import httpx
r = httpx.post("{{BASE}}/v1/intent", headers={"X-API-Key": "TU_API_KEY"},
json={"query": "buscar escenas comparables y señales candidatas de agua nueva en Pehuajó"},
timeout=120)
d = r.json()
print(d["interpretation"], d["selected_satellite"], d["explanation"], sep="\n")
const d = await (await fetch("{{BASE}}/v1/intent", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "TU_API_KEY" },
body: JSON.stringify({ query: "buscar escenas comparables y señales candidatas de agua nueva en Pehuajó" }),
})).json();
console.log(d.inferred_params, d.selected_satellite);
Agente de IA: pregunta libre sobre una zona
La puerta conversacional de ORBITAL GRID: mandás una pregunta en tu idioma y una zona; el agente elige el producto y los satélites, calcula con píxeles/metadata reales y responde en lenguaje simple con semáforo. El LLM redacta pero nunca calcula: sin dato real, la respuesta lo dice.
| Campo | Tipo | Descripción |
|---|---|---|
question | str | Qué necesitás saber, en tus palabras (3–1000 caracteres). Obligatorio. |
lat / lon | float | Punto de interés (lat -90 a 90, lon -180 a 180). Usá esto o bbox. |
bbox | [o,s,e,n] | Zona explícita [oeste, sur, este, norte] en WGS84, alternativa al punto (p. ej. la envolvente de un AOI subido con /v1/aoi/parse). Tope ~10.000 km². |
buffer_km | float | Radio del área alrededor del punto (0–50, default 5). Ignorado si usás bbox. |
lang | es · en | Idioma de la respuesta (default es). |
curl -s -X POST {{BASE}}/v1/agent/ask \
-H "Content-Type: application/json" -H "X-API-Key: TU_API_KEY" \
-d '{"question":"¿cómo está mi campo esta semana?","lat":-33.90,"lon":-60.60}'
import httpx
# Zona por bbox (alternativa al punto): p. ej. la envolvente de un AOI subido.
r = httpx.post("{{BASE}}/v1/agent/ask",
headers={"X-API-Key": "TU_API_KEY"},
json={"question": "¿qué escenas y señales de agua requieren revisión?",
"bbox": [-60.70, -34.00, -60.50, -33.80]},
timeout=120)
d = r.json()
print(d["status"], d["semaphore"], d["answer"])
const d = await (await fetch("{{BASE}}/v1/agent/ask", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "TU_API_KEY" },
body: JSON.stringify({ question: "¿cómo está mi campo?", lat: -33.90, lon: -60.60 }),
})).json();
console.log(d.status, d.semaphore, d.provenance);
La respuesta trae status (delivered · no_data · unavailable · requires_credential), answer en lenguaje consumidor, semaphore (verde · amarillo · rojo), confidence, provenance (scene_id, satellite, datetime) y technical_detail con el producto, el satélite elegido y el porqué. Cada número del answer sale de píxeles o metadata reales.
Capa de analytics
Catálogo de capacidades con su escalera de madurez. registered o searchable no significa motor validado ni claim comercial autorizado.
Demo local de señales SAR por celdas
Endpoint de investigación geocercado a Pergamino. El valor histórico publicado se obtuvo sobre 326 celdas rectangulares y una referencia NDVI proxy, no sobre lotes ni verdad de campo. No autoriza claims de siembra, especie o fecha.
| Campo | Tipo | Descripción |
|---|---|---|
bbox | [w,s,e,n] | AOI en WGS84. Obligatorio. |
min_confidence | 0–1 | Solo lotes con confianza ≥ este valor (default 0). |
include_geometry | bool | false = solo propiedades, sin polígonos (default true). |
curl -s -X POST {{BASE}}/v1/analytics/fields \
-H "Content-Type: application/json" -H "X-API-Key: TU_API_KEY" \
-d '{"bbox":[-60.65,-33.90,-60.60,-33.85],"min_confidence":0.8}'
import httpx
r = httpx.post("{{BASE}}/v1/analytics/fields",
headers={"X-API-Key": "TU_API_KEY"},
json={"bbox": [-60.65, -33.90, -60.60, -33.85], "min_confidence": 0.8},
timeout=60)
d = r.json()
if d["status"] == "delivered":
print(d["summary"]) # salida experimental; requiere revisión humana
geojson = d["result"] # celdas de demo, no límites de parcelas
else:
print(d["message"]) # AOI aún no procesada — respuesta honesta
const d = await (await fetch("{{BASE}}/v1/analytics/fields", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "TU_API_KEY" },
body: JSON.stringify({ bbox: [-60.65, -33.90, -60.60, -33.85], min_confidence: 0.8 }),
})).json();
console.log(d.status, d.summary ?? d.message);
La salida es una demo estática con VV/VH/RVI y referencia óptica proxy. No representa lotes reales ni se comercializa como siembra detectada. Fuera del piloto devuelve accepted_on_demand; el SKU vendible entrega series descriptivas revisadas por analista.
Subí tu área de interés desde un archivo
Convertí un archivo geográfico en un polígono WGS84 normalizado, listo para el geometry/bbox del resto de la API. Enviá el archivo como multipart en el campo file, o mandá un JSON con wkt o geojson. Nunca inventa geometría: si el archivo no tiene un polígono válido, responde ok:false con la causa en error.
| Campo | Tipo | Descripción |
|---|---|---|
file | multipart | Archivo KML · KMZ · GeoJSON · Shapefile (ZIP) · WKT · CSV de puntos. Alternativa al cuerpo JSON. |
wkt | str (JSON) | Un POLYGON / MULTIPOLYGON en WKT (cuerpo JSON, alternativa al archivo). |
geojson | obj (JSON) | Un objeto GeoJSON (Feature · FeatureCollection · geometría) en el cuerpo JSON. |
Respuesta AOIResult: ok, geometry (Polygon/MultiPolygon WGS84), bbox [oeste, sur, este, norte], area_km2, vertex_count, simplified, source_format, notes[] y error.
curl -s -X POST {{BASE}}/v1/aoi/parse \
-H "X-API-Key: TU_API_KEY" \
-F "file=@mi_campo.kml"
import httpx
with open("mi_campo.geojson", "rb") as f:
r = httpx.post("{{BASE}}/v1/aoi/parse",
headers={"X-API-Key": "TU_API_KEY"},
files={"file": ("mi_campo.geojson", f, "application/geo+json")},
timeout=60)
aoi = r.json()
if aoi["ok"]:
print(aoi["area_km2"], "km²", aoi["vertex_count"], "vértices")
bbox = aoi["bbox"] # [oeste, sur, este, norte]
else:
print("No se pudo:", aoi["error"])
// Cuerpo JSON con WKT (alternativa a subir un archivo)
const aoi = await (await fetch("{{BASE}}/v1/aoi/parse", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "TU_API_KEY" },
body: JSON.stringify({ wkt: "POLYGON((-60.7 -34.0,-60.5 -34.0,-60.5 -33.8,-60.7 -33.8,-60.7 -34.0))" }),
})).json();
console.log(aoi.ok, aoi.area_km2, aoi.bbox);
413); área máxima 500.000 km² (por encima se rechaza con ok:false y la causa); polígonos de más de 2000 vértices se simplifican conservando la forma (flag simplified + nota); guardas anti zip-bomb en KMZ/Shapefile. Coordenadas fuera del rango WGS84 (archivo proyectado) → rechazo honesto pidiendo reproyectar a EPSG:4326. Un CSV de puntos arma el convex hull (envolvente) y lo aclara en notes.Exportá los contornos de escena en GeoJSON o Shapefile
Exportá los footprints de un conjunto de escenas. Pasá scenes (una lista de escenas, p. ej. las de /v1/imagery/search) o search (un cuerpo ImagerySearchRequest: el motor de ruteo corre y exporta lo que devuelve). Elegí el formato con ?format=geojson (default) o ?format=shapefile.
| Campo | Tipo | Descripción |
|---|---|---|
scenes | list | Lista de escenas a exportar (esto o search). |
search | obj | Un ImagerySearchRequest; el motor rutea y exporta las escenas que encuentra. |
format | query | geojson (default) · shapefile. Va como query param en la URL. |
curl -s -X POST "{{BASE}}/v1/exports/footprints?format=geojson" \
-H "Content-Type: application/json" -H "X-API-Key: TU_API_KEY" \
-d '{"search":{"bbox":[-60.70,-34.00,-60.50,-33.80],
"datetime_from":"2026-04-01","datetime_to":"2026-07-06",
"data_type":"optical","limit":10}}' \
-o footprints.geojson
import httpx
# scenes = res["scenes"] # p. ej. la respuesta de /v1/imagery/search
r = httpx.post("{{BASE}}/v1/exports/footprints",
params={"format": "shapefile"},
headers={"X-API-Key": "TU_API_KEY"},
json={"scenes": scenes},
timeout=120)
if r.headers.get("X-Export-Fallback") == "geojson":
print("Shapefile no disponible; llegó GeoJSON:", r.headers.get("X-Export-Note"))
open("footprints.zip", "wb").write(r.content)
const r = await fetch("{{BASE}}/v1/exports/footprints?format=geojson", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "TU_API_KEY" },
body: JSON.stringify({ scenes }), // o { search: { bbox, datetime_from, ... } }
});
const fc = await r.json(); // FeatureCollection (application/geo+json)
console.log(fc.features.length, "footprints");
X-Export-Fallback: geojson (y X-Export-Note con la causa) en vez de romper. El GeoJSON sale como application/geo+json; el shapefile como un .zip (shp+shx+dbf+prj+README).Solicitar API Key o hablar con Orbital Grid
Para usar la API necesitás una clave personal. Pedila acá y el equipo de Orbital Grid te contacta para darte de alta y ayudarte a arrancar.
Cuenta y uso
Uso medido de tu key: total, hoy, por día (últimos 30) y por endpoint. Es la misma medición que alimenta la facturación.
Datos de la key: prefijo visible, plan, rate limit, fecha de alta.
Revoca la key con la que autenticás. Es irreversible: solicitá una nueva si la necesitás.
curl -s {{BASE}}/v1/account/usage -H "X-API-Key: TU_API_KEY"
import httpx
u = httpx.get("{{BASE}}/v1/account/usage",
headers={"X-API-Key": "TU_API_KEY"}, timeout=30).json()
print(u["usage"]["total"], "requests")
const u = await (await fetch("{{BASE}}/v1/account/usage",
{ headers: { "X-API-Key": "TU_API_KEY" } })).json();
console.log(u.usage.total, "requests");
Errores y límites
| Código | Significado | Qué hacer |
|---|---|---|
401 | Key faltante, inválida o revocada. | Verificá el header X-API-Key; si todavía no tenés una, solicitala en Solicitar API Key. |
422 | Parámetros inválidos (bbox mal formado, enum desconocido…). | El detalle indica el campo exacto. |
429 | Rate limit de tu plan excedido. | Respetá el header Retry-After. |
503 | Capacidad temporalmente no disponible. | Reintentá con backoff; mirá /health. |
Los catálogos upstream pueden estar caídos: la plataforma lo reporta en catalogs_status y en /health en vez de inventar resultados.