# SateAIs API Docs (Full) --- --- > Full Markdown bundle of the SateAIs API Docs for LLM ingestion. --- --- # SateAIs API > AI-powered satellite data analysis — detect ships, oil slicks, new buildings, and time-series change via a simple REST API. Source: https://docs.spcsft.com/ The SateAIs API is a RESTful service at `https://api.spcsft.com` that turns satellite imagery into structured GeoJSON detections. Submit an area of interest, and receive AI-powered analysis results ready to render on any map. ## Get started [#get-started] - [Quickstart](https://docs.spcsft.com/quickstart.md): Run your first detection in under five minutes with a single curl command. - [Authentication](https://docs.spcsft.com/authentication.md): Generate an API key in the Console and authenticate your requests. ## SDKs [#sdks] - [Python SDK](https://docs.spcsft.com/sdk/python.md): pip install sateais — typed client, polling helper, and a sateais CLI. - [JavaScript SDK](https://docs.spcsft.com/sdk/javascript.md): npm install @sateais/sdk — zero-dependency, ESM + CommonJS, bundled types. ## Detection capabilities [#detection-capabilities] - [Ship detection](https://docs.spcsft.com/api-reference/analyze/ship.md): Detect vessels from Sentinel-1 SAR imagery. Typical runtime ~30 min. - [Oil slick detection](https://docs.spcsft.com/api-reference/analyze/oilslick.md): Identify oil spills on the sea surface. Typical runtime ~50 min. - [New building detection](https://docs.spcsft.com/api-reference/analyze/newbuilding.md): Find newly constructed structures across two time periods. - [Disappeared building detection](https://docs.spcsft.com/api-reference/analyze/disappearbuilding.md): Identify buildings missing between two periods — post-disaster damage. - [Time-series change](https://docs.spcsft.com/api-reference/analyze/timeseries.md): Detect surface change between two observations of the same area. ## Core concepts [#core-concepts] - [How it works](https://docs.spcsft.com/concepts/how-it-works.md): Async job lifecycle — submit, poll, fetch. - [Pricing](https://docs.spcsft.com/pricing/plans.md): Free, Pro, and Enterprise plans. Free includes 500 credits/month. - [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md): Per-key request quotas and burst behavior. ## AI agents [#ai-agents] - [MCP (Model Context Protocol)](https://docs.spcsft.com/mcp.md): Connect Claude, ChatGPT, and other MCP-compatible agents to SateAIs detections. - [Authentication](https://docs.spcsft.com/authentication.md): API keys secure the REST API; MCP connects with OAuth sign-in. ## Resources [#resources] - [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md): Define areas of interest as WKT polygons. - [Errors](https://docs.spcsft.com/errors.md): Error codes, retry guidance, and troubleshooting. --- # Quickstart > Run your first satellite analysis with SateAIs in under five minutes. Source: https://docs.spcsft.com/quickstart This guide walks you through submitting a detection job, polling for its status, and downloading the GeoJSON result. ## Prefer an SDK? [#prefer-an-sdk] The official [Python](https://docs.spcsft.com/sdk/python.md) and [JavaScript](https://docs.spcsft.com/sdk/javascript.md) SDKs do the whole submit → poll → download flow in a few lines: ```python from sateais import Client client = Client() # reads SATEAIS_API_KEY job = client.analyze.oilslick(scene_id="S1A_IW_GRDH_...") result = client.jobs.wait(job.job_id) # poll until completed print(len(result["features"]), "detections") ``` ```typescript import { Client } from "@sateais/sdk"; const client = new Client(); // reads SATEAIS_API_KEY const job = await client.analyze.oilslick({ scene_id: "S1A_IW_GRDH_..." }); const result = await client.jobs.wait(job.job_id); // poll until completed console.log(result.features.length, "detections"); ``` The rest of this guide uses `curl` to show the underlying REST calls. ## Prerequisites [#prerequisites] * A [SateAIs API Console account](https://console.spcsft.com) * An API key (`sk_live_...`) — see [Authentication](https://docs.spcsft.com/authentication.md) * `curl` available in your shell ## 1. Submit a detection job [#1-submit-a-detection-job] Submit an oil slick detection job by scene ID. Scene IDs come from [ASF Search](https://search.asf.alaska.edu/#/) — filter **File Type = GRD** (SateAIs only accepts Sentinel-1 GRD products). ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/oilslick \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "scene_id": "S1A_IW_GRDH_1SDV_20260312T150000_20260312T150025_054876_069DEF_5678" }' ``` You will receive a `job_id` and an initial status: ```json { "job_id": "351e635d-7c25-4ae8-a2a5-60c01a6f434c", "status": "pending", "created_at": "2026-03-12T15:00:00Z", "completed_at": null, "result_path": null, "error": null } ``` ## 2. Poll the job status [#2-poll-the-job-status] Check the job status about **once per minute** until `status` becomes `completed`. Runtime depends on the analysis type and analyzed area — see [How it works](https://docs.spcsft.com/concepts/how-it-works.md#recommended-polling-interval) for per-endpoint estimates. ```bash curl https://api.spcsft.com/api/v1/jobs/351e635d-7c25-4ae8-a2a5-60c01a6f434c \ -H "Authorization: Bearer sk_live_xxxxx" ``` > **Note** > > Detection jobs run asynchronously; runtime depends on the analysis type and area. See [How it works](https://docs.spcsft.com/concepts/how-it-works.md) for per-endpoint estimates and recommended polling intervals. ## 3. Download the result [#3-download-the-result] Once the job completes, fetch the GeoJSON: ```bash curl -L https://api.spcsft.com/api/v1/jobs/351e635d-7c25-4ae8-a2a5-60c01a6f434c/result.geojson \ -H "Authorization: Bearer sk_live_xxxxx" \ -o result.geojson ``` The response is a standard GeoJSON `FeatureCollection` in `EPSG:4326`. You can view results directly on the map in the [SateAIs API Console](https://console.spcsft.com), or open the file in any GIS tool. > **Warning** > > Results are available for **30 days** after completion. After that, the endpoint returns `410 Gone`. Download and archive results you need to retain. ## Next steps [#next-steps] - [Authentication](https://docs.spcsft.com/authentication.md): Manage API keys securely. - [How it works](https://docs.spcsft.com/concepts/how-it-works.md): Deep-dive into the async job model. - [Analysis APIs](https://docs.spcsft.com/api-reference/analyze/ship.md): Parameters for all five analysis types. - [Errors](https://docs.spcsft.com/errors.md): Handle errors and retries. --- # Authentication > Authenticate SateAIs API requests using Bearer API keys. Source: https://docs.spcsft.com/authentication The SateAIs API uses Bearer authentication with API keys issued from the [SateAIs API Console](https://console.spcsft.com). ## Getting an API key [#getting-an-api-key] 1. Sign in to the [SateAIs API Console](https://console.spcsft.com). 2. Open **Settings → API Keys** and click **Create API key**. 3. Copy the displayed key. It starts with `sk_live_`. The full key can be **re-displayed anytime** from the Console — you don't need to store it elsewhere at creation time. > **Warning: Rotate on leak** > > If an API key is exposed — for example pasted into a generative AI tool, committed to a public repository, or shared in chat — **rotate it immediately** from the Console. Leaked keys can consume your credits until revoked. ## Sending authenticated requests [#sending-authenticated-requests] Include the key in the `Authorization` header on every request: ```http Authorization: Bearer sk_live_xxxxx ``` Example: ```bash curl https://api.spcsft.com/api/v1/jobs/YOUR_JOB_ID \ -H "Authorization: Bearer sk_live_xxxxx" ``` ## Authentication errors [#authentication-errors] | Status | Code | Meaning | | ------ | -------------- | ------------------------------------------------------------------------------------------- | | `401` | `UNAUTHORIZED` | Missing, malformed, or revoked API key. Check the `Authorization` header. | | `403` | `FORBIDDEN` | Key is valid but lacks permission for the endpoint. Check plan entitlements in the Console. | For all error codes, see [Errors](https://docs.spcsft.com/errors.md). ## Next steps [#next-steps] - [Quickstart](https://docs.spcsft.com/quickstart.md): Make your first authenticated request. - [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md): Understand request and concurrency limits. --- # Concepts > Core concepts for working with SateAIs asynchronous satellite analysis jobs. Source: https://docs.spcsft.com/concepts - [How it works](https://docs.spcsft.com/concepts/how-it-works.md): Async job lifecycle — submit, poll, fetch. - [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md): Define areas of interest as WKT polygons. - [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md): Per-key request quotas and concurrency limits. --- # How it works > Understand the asynchronous job lifecycle used by every SateAIs detection API. Source: https://docs.spcsft.com/concepts/how-it-works Satellite imagery analysis takes minutes to tens of minutes depending on the model and area. Every SateAIs detection endpoint is **asynchronous**: you submit a job, poll for its status, then fetch the result. ## The three-step lifecycle [#the-three-step-lifecycle] ``` 1. POST /api/v1/analyze/{type} → receive job_id (status = "pending") 2. GET /api/v1/jobs/{job_id} → poll until status = "completed" 3. GET /api/v1/jobs/{job_id}/result.geojson → fetch the GeoJSON result (302 redirect to S3) ``` ## Job statuses [#job-statuses] | Status | Description | | ------------ | --------------------------------------------------------------------- | | `pending` | Job received, queued for processing. | | `processing` | Currently running inference. | | `completed` | Finished successfully. Result is available for 30 days. | | `failed` | Failed. See the `error` field for the reason. No credits are charged. | ## Recommended polling interval [#recommended-polling-interval] Poll **every \~1 minute**. Polling faster increases load without accelerating completion and may trigger rate limits. Runtime depends mostly on **analyzed area**. The ranges below reflect small-to-large AOIs observed in production. Submit a small test job first to gauge the upper bound for your workload. | Detection | Typical runtime | Suggested first poll | | ----------------- | ------------------------- | ----------------------- | | ship | \~15 min | 5 min after submission | | oilslick | \~50 min | 10 min after submission | | newbuilding | 1–40 min (area-dependent) | 5 min after submission | | disappearbuilding | 1–40 min (area-dependent) | 5 min after submission | | timeseries | 1–15 min (area-dependent) | 2 min after submission | > **Note** > > `ship` / `oilslick` process a single Sentinel-1 scene each, so runtime is relatively stable. `newbuilding` / `disappearbuilding` / `timeseries` scale with the polygon area — a 1 km² AOI finishes in about a minute, while approaching the per-endpoint size cap can take tens of minutes. ## Result retention [#result-retention] Results are stored for **30 days after completion**. After that, `GET /api/v1/jobs/{job_id}/result.geojson` returns `410 Gone`. The job metadata itself (status, timestamps) is retained indefinitely. ## Failure handling [#failure-handling] If a job fails: * `status` transitions to `failed`. * The `error` field contains a machine-readable `code` and human-readable `message`. * **No credits are charged** for failed jobs. * Clients should surface the error code to users and optionally retry. See [Errors](https://docs.spcsft.com/errors.md) for the full list of error codes. ## Next steps [#next-steps] - [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md): Concurrency and throughput limits. - [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md): How to specify areas of interest. - [Jobs API](https://docs.spcsft.com/api-reference/jobs/get-job.md): Full reference for job status and result endpoints. --- # Polygons & AOI > How to specify areas of interest using WKT polygons in EPSG:4326 (WGS84). Source: https://docs.spcsft.com/concepts/polygons-and-aoi The SateAIs API uses **WKT (Well-Known Text) polygons** to define areas of interest (AOI). ## Coordinate reference system [#coordinate-reference-system] All polygons must use **`EPSG:4326` (WGS84)**: longitude/latitude in decimal degrees. Other CRS (UTM, Web Mercator, etc.) are not accepted. ## Polygon format [#polygon-format] Use a single WKT `POLYGON` with coordinates in `(longitude latitude)` order. The ring must be **closed** (first and last point identical). ``` POLYGON(( 139.75 35.55, 139.80 35.55, 139.80 35.60, 139.75 35.60, 139.75 35.55 )) ``` > **Note** > > WKT uses **`lon lat`** ordering (the opposite of GeoJSON's common `[lat, lon]` notation in some UIs). Double-check your inputs. ## Size constraints [#size-constraints] | Detection | Polygon size limit | | ------------------- | -------------------------------------- | | `ship` | No hard limit (single scene per job) | | `oilslick` | No hard limit (single scene per job) | | `newbuilding` | **30,000 km²** | | `disappearbuilding` | **30,000 km²** | | `timeseries` | **50 km²** (approximately 7 km × 7 km) | `newbuilding`, `disappearbuilding`, and `timeseries` enforce hard size caps at the API layer. For `ship` / `oilslick`, the polygon can be arbitrarily large — but runtime grows with area (see [Recommended polling interval](https://docs.spcsft.com/concepts/how-it-works.md#recommended-polling-interval)). > **Note: How AOI is processed** > > When you submit a `polygon` (AOI), SateAIs automatically selects a Sentinel-1 **GRD** scene close to `date` on the backend, then clips the scene to the region that **intersects** with your polygon. Only the intersecting portion is analyzed — coverage outside the scene footprint is skipped. The analysed portion is reported back on the job as [`coverage`](https://docs.spcsft.com/api-reference/jobs/get-job.md#coverage), and you can check it before submitting with [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md). Exceeding the size cap returns `400 VALIDATION_ERROR` **before any credits are consumed**. Split large areas into multiple jobs. ## Polygon vs scene ID [#polygon-vs-scene-id] For `ship` and `oilslick`, you can choose between two input patterns: * **`scene_id` (Pattern A)** — process the entire Sentinel-1 **GRD** scene. Use when you already know the scene or want scene-wide analysis. * **`polygon` + `date` (Pattern B)** — SateAIs automatically selects the nearest Sentinel-1 **GRD** scene for the given date and clips to the polygon. Lower credit consumption for localized analysis. See [Analysis APIs](https://docs.spcsft.com/api-reference/analyze/ship.md) for the full parameter matrix. ## Sanity checks [#sanity-checks] * Ensure the polygon is **planar and non-self-intersecting**. * Keep coordinate precision reasonable (6 decimals ≈ 0.1 m is more than enough). * If your AOI crosses the antimeridian (180°/-180°), split into two polygons. ## Next steps [#next-steps] - [Analysis APIs](https://docs.spcsft.com/api-reference/analyze/ship.md): Full parameter reference for each endpoint. --- # Rate limits > Request and concurrency limits for the SateAIs API. Source: https://docs.spcsft.com/concepts/rate-limits The SateAIs API enforces limits to protect platform stability. ## Request throughput [#request-throughput] | Limit | Value | Notes | | ------------------- | ---------------------- | ----------------------------------- | | Requests per second | **10 rps** | Burst up to 50 | | Enforcement | API Gateway throttling | `429 RATE_LIMIT_EXCEEDED` on excess | ## Concurrent jobs [#concurrent-jobs] | Limit | Value | Notes | | --------------------------- | ----- | ----------- | | Concurrent jobs per API key | **5** | Per-key cap | ## Handling `429 RATE_LIMIT_EXCEEDED` [#handling-429-rate_limit_exceeded] When you exceed a limit, the API returns: ```json { "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Too many concurrent GPU jobs (5/5). Please retry later." } } ``` > **Warning: System congestion** > > `429 RATE_LIMIT_EXCEEDED` can also be returned when the SateAIs platform is under heavy load — even if you are below the per-key concurrency limit. Always implement retry with backoff. Recommended handling: * **Exponential backoff** on retries (start 1 s, cap 30 s). * **Queue in your application layer** rather than submitting all jobs at once. * **Monitor the `429` rate** in your telemetry; sustained rate limiting signals you should batch differently. > **Tip** > > If you consistently hit concurrency limits, contact [support](mailto:console-support@spcsft.com) about a higher tier. ## Next steps [#next-steps] - [Errors](https://docs.spcsft.com/errors.md): Full error reference and recommended retries. - [How it works](https://docs.spcsft.com/concepts/how-it-works.md): Polling cadence and job lifecycle. --- # Errors > HTTP status codes, error codes, and recommended handling for the SateAIs API. Source: https://docs.spcsft.com/errors ## Synchronous errors (4xx / 5xx) [#synchronous-errors-4xx--5xx] Immediate error responses (e.g. invalid request, auth failure) share the same envelope: ```json { "error": { "code": "INSUFFICIENT_CREDITS", "message": "Insufficient credit balance" } } ``` ## Asynchronous job failures [#asynchronous-job-failures] When a job transitions to `failed`, the error details are returned as flat fields on the [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md) response: ```json { "job_id": "351e635d-7c25-4ae8-a2a5-60c01a6f434c", "status": "failed", "created_at": "2026-03-12T15:00:00Z", "completed_at": "2026-03-12T15:31:15Z", "result_path": null, "error_code": "VALIDATION_ERROR", "error_message": "Polygon area (52.6 km²) exceeds 50 km² limit for endpoint 'timeseries'" } ``` For 5xx failures, `error_message` returns a fixed string to avoid leaking internal details. The string depends on the cause, because the action you should take differs: | `error_code` | `error_message` | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `EXTERNAL_SERVICE_ERROR` (502) | `The analysis could not be completed due to a temporary issue with an external satellite data service. Your credits have been automatically refunded. Please try again later.` | | `TIMEOUT` (504) | `The analysis could not be completed in time. Your credits have been automatically refunded. Please try again later.` | | Anything else | `An internal error occurred. Please try again later or contact support.` | ## Error codes [#error-codes] | HTTP | Code | Meaning | Recommended action | | ----- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | `400` | `VALIDATION_ERROR` | Invalid request body, bad parameter, or a per-endpoint cap exceeded — polygon area (`newbuilding` / `disappearbuilding` ≤ 30,000 km², `timeseries` ≤ 50 km²) or date range (`timeseries` ≤ 3 years). Rejected **before credits are consumed**. | Fix the request. Do not retry unchanged. | | `401` | `UNAUTHORIZED` | Missing, malformed, or revoked API key. | Check `Authorization` header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits for the job. | Purchase credits in the Console. | | `403` | `FORBIDDEN` | Key lacks permission for this endpoint. | Check plan entitlements. | | `404` | `NOT_FOUND` | Job ID does not exist. | Verify the UUID. | | `404` | `SCENE_NOT_FOUND` | No Sentinel-1 scene within ±14 days of the requested date. | Widen date range or pick another date. | | `404` | `INSUFFICIENT_SCENES` | Fewer than two usable scenes found for `newbuilding` / `disappearbuilding` / `timeseries`, or the available scenes lack the orbit metadata needed to compare them. | Widen the date range. | | `410` | `GONE` | Result file is past the 30-day retention window. | Result is unavailable; resubmit if needed. | | `429` | `RATE_LIMIT_EXCEEDED` | Request rate or concurrent-job limit exceeded. | Back off exponentially. | | `500` | `INTERNAL_ERROR` | Unexpected server error. | Retry with backoff; contact [support](mailto:console-support@spcsft.com) if persistent. | | `502` | `EXTERNAL_SERVICE_ERROR` | Upstream satellite-data provider error. Credits are refunded automatically. | Retry after a few minutes. | | `504` | `TIMEOUT` | Request timed out. Credits are refunded automatically. | Retry with backoff. | > **Note: Using an SDK?** > > The [Python](https://docs.spcsft.com/sdk/python/reference.md#exceptions) and [JavaScript](https://docs.spcsft.com/sdk/javascript/reference.md#errors) SDKs raise typed exceptions for each status code above — for example `402` becomes `InsufficientCreditsError` and a failed job becomes `JobFailedError`. ## Warnings [#warnings] Warnings are **not failures**. A job that returns warnings has `status: "completed"` and `error_code: null`, and the result is available as usual. They tell you that the imagery the API selected differs from what you asked for, so you can decide whether to trust the result or adjust the request. They appear in the `warnings` array of [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md#warnings). [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md) returns the two that can be determined before submission: `LOW_AOI_COVERAGE` and `CREDITS_NOT_ESTIMABLE`. | Code | Meaning | Recommended action | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | | `SCENE_DATE_FAR_FROM_REQUESTED` | A scene used is 60 days or more away from the date you requested. The exact gap is not reported — compare `scene_selection.first_month` / `last_month` with your requested dates. | Suitable imagery may not exist near your dates. Widen or shift the date range. | | `PERIOD_PARTIALLY_COVERED` | The observations span less than 80% of the requested period. The percentage in the message is rounded down to the nearest 10%. | Split the period into shorter requests, or accept the narrower span. | | `LOW_AOI_COVERAGE` | The imagery covers less than 90% of your AOI. | Part of the AOI has no data. Shrink or shift the AOI. | | `FEW_CANDIDATE_SCENES` | Only the bare minimum number of scenes was available. | Widen the date range for a more reliable result. | | `ALTERNATIVE_GROUP_AVAILABLE` | Other imagery covers a later part of the requested period, but could not be combined with the imagery used. | Split the period into separate requests to analyse both parts. | | `SCENE_METADATA_INCOMPLETE` | Some candidate scenes were excluded because required orbit metadata was missing. | Usually safe to ignore. Contact [support](mailto:console-support@spcsft.com) if results look wrong. | | `CREDITS_NOT_ESTIMABLE` | Returned only by [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): the credit cost depends on the extent of the selected scene, which is not known before submission. `credits.estimated` is `null` — this does **not** mean the job is free. | Use `polygon` input if you need a pre-submission estimate. | > **Note** > > Change detection compares scenes captured from the same viewing geometry. When your period spans imagery from different orbits, one request cannot cover all of it — this is the usual cause of `PERIOD_PARTIALLY_COVERED` and `ALTERNATIVE_GROUP_AVAILABLE`. ## Retrying safely [#retrying-safely] * **Idempotent GETs** (`GET /api/v1/jobs/*`) — safe to retry unconditionally. * **POST detection requests** — if you receive `5xx` or a network error without a response, check whether a `job_id` was created before retrying to avoid duplicate jobs. * Use **exponential backoff** (start 1 s, cap 30 s, max 5 attempts) for `429`, `5xx`, and `504`. ## Tracing a failed job [#tracing-a-failed-job] If a job transitions to `failed`: 1. Call [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md) to read `error_code` and `error_message`. 2. Map `error_code` to the table above for recommended actions. 3. Surface `error_message` to the end user where appropriate. > **Note** > > The `error` field is deprecated and returns the same value as `error_code`. Use `error_code` + `error_message` in new integrations. Failed jobs **do not consume credits**. ## Next steps [#next-steps] - [Authentication](https://docs.spcsft.com/authentication.md): Fix 401/403 errors. - [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md): Understand 429 handling. - [How it works](https://docs.spcsft.com/concepts/how-it-works.md): Async job lifecycle. --- # API Reference > Full reference for SateAIs detection endpoints and job management APIs. Source: https://docs.spcsft.com/api-reference SateAIs exposes five asynchronous detection endpoints and three job-management endpoints. All requests share the same base URL, authentication, and response format. ## Base URL [#base-url] ``` https://api.spcsft.com ``` ## Authentication [#authentication] Every request must include a Bearer API key in the `Authorization` header. See [Authentication](https://docs.spcsft.com/authentication.md) for details. ## Detections [#detections] - [Ship detection](https://docs.spcsft.com/api-reference/analyze/ship.md): Detect vessels from Sentinel-1 SAR imagery. - [Oil slick detection](https://docs.spcsft.com/api-reference/analyze/oilslick.md): Identify oil spills on the sea surface. - [New building detection](https://docs.spcsft.com/api-reference/analyze/newbuilding.md): Find newly constructed structures across two time periods. - [Disappeared building detection](https://docs.spcsft.com/api-reference/analyze/disappearbuilding.md): Identify buildings missing between two periods — post-disaster damage. - [Time-series change](https://docs.spcsft.com/api-reference/analyze/timeseries.md): Detect surface change between two observations. ## Job management [#job-management] - [List jobs](https://docs.spcsft.com/api-reference/jobs/list-jobs.md): List your recent jobs, filtered by status or endpoint. - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Poll a job until completion. - [Get job result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Fetch the GeoJSON FeatureCollection for a completed job. --- # Analysis APIs > Endpoints for submitting SateAIs satellite detection jobs. Source: https://docs.spcsft.com/api-reference/analyze - [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): Check the analysed area and credit cost before submitting. - [Ship detection](https://docs.spcsft.com/api-reference/analyze/ship.md): Detect vessels from Sentinel-1 SAR imagery. - [Oil slick detection](https://docs.spcsft.com/api-reference/analyze/oilslick.md): Identify oil spills on the sea surface. - [New building detection](https://docs.spcsft.com/api-reference/analyze/newbuilding.md): Find newly constructed structures across two time periods. - [Disappeared building detection](https://docs.spcsft.com/api-reference/analyze/disappearbuilding.md): Detect buildings missing after a disaster between two periods. - [Time-series change](https://docs.spcsft.com/api-reference/analyze/timeseries.md): Detect surface change between two observations. --- # Disappeared building detection > Detect buildings that have disappeared between two periods of Sentinel-1 imagery — useful for post-disaster damage estimation. Source: https://docs.spcsft.com/api-reference/analyze/disappearbuilding **POST** `/api/v1/analyze/disappearbuilding` Detect buildings that were present in an earlier scene but missing in a later scene. Designed for post-disaster damage assessment (earthquake, flood, fire, etc.). Requires two scenes (before/after), so only the `polygon` + date-range input pattern is supported. | | | | ------------------- | --------------------------------------------------------- | | Supported satellite | Sentinel-1 (RTC — terrain-corrected, selected by the API) | | Typical runtime | Depends on AOI size | | Input pattern | `polygon` + `date_start` + `date_end` only | | Polygon area limit | **30,000 km²** | ## Request [#request] ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/disappearbuilding \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "polygon": "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", "date_start": "2024-01-01", "date_end": "2024-12-01" }' ``` ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ------------------------------- | -------- | ------------------------- | | `satellite_id` | `string` | Yes | Must be `"sentinel-1"`. | | `polygon` | `string` (WKT) | Yes | AOI polygon in EPSG:4326. | | `date_start` | `string` (`YYYY-MM-DD`) | Yes | Pre-event period start. | | `date_end` | `string` (`YYYY-MM-DD`) | Yes | Post-event period end. | | `orbit_direction` | `"ascending"` \| `"descending"` | No | Filter scenes by orbit. | > **Note** > > SateAIs picks one scene from the earlier period and one from the later period, both using the same path/frame for comparability. The model ensemble compares the pair and emits polygons of disappeared structures. ## Constraints [#constraints] | Constraint | Value | | ------------- | --------------------------------------------------------------------------------------- | | Polygon area | ≤ **30,000 km²** — exceeding returns `400 VALIDATION_ERROR` before credits are consumed | | Input pattern | Only `polygon` + `date_start` + `date_end` (scene IDs are not accepted) | ## Next steps [#next-steps] - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Poll for completion. - [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Download the GeoJSON. - [New building detection](https://docs.spcsft.com/api-reference/analyze/newbuilding.md): Detect newly-constructed buildings. - [Errors](https://docs.spcsft.com/errors.md): Error codes for this endpoint. --- # New building detection > Detect newly-constructed buildings by comparing two periods of Sentinel-1 imagery. Source: https://docs.spcsft.com/api-reference/analyze/newbuilding **POST** `/api/v1/analyze/newbuilding` Detect buildings that were newly constructed between two time ranges. Requires two scenes (before/after), so only the `polygon` + date-range input pattern is supported. | | | | ------------------- | --------------------------------------------------------- | | Supported satellite | Sentinel-1 (RTC — terrain-corrected, selected by the API) | | Typical runtime | Depends on AOI size | | Input pattern | `polygon` + `date_start` + `date_end` only | | Polygon area limit | **30,000 km²** | ## Request [#request] ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/newbuilding \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "polygon": "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", "date_start": "2024-01-01", "date_end": "2026-03-01" }' ``` ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ------------------------------- | -------- | ------------------------- | | `satellite_id` | `string` | Yes | Must be `"sentinel-1"`. | | `polygon` | `string` (WKT) | Yes | AOI polygon in EPSG:4326. | | `date_start` | `string` (`YYYY-MM-DD`) | Yes | Earlier period start. | | `date_end` | `string` (`YYYY-MM-DD`) | Yes | Later period end. | | `orbit_direction` | `"ascending"` \| `"descending"` | No | Filter scenes by orbit. | > **Note** > > SateAIs picks one scene from the earlier period and one from the later period, both using the same path/frame for comparability. ## Constraints [#constraints] | Constraint | Value | | ------------- | --------------------------------------------------------------------------------------- | | Polygon area | ≤ **30,000 km²** — exceeding returns `400 VALIDATION_ERROR` before credits are consumed | | Input pattern | Only `polygon` + `date_start` + `date_end` (scene IDs are not accepted) | ## Next steps [#next-steps] - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Poll for completion. - [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Download the GeoJSON. - [Time-series change](https://docs.spcsft.com/api-reference/analyze/timeseries.md): Detect changes over many scenes. - [Errors](https://docs.spcsft.com/errors.md): Error codes for this endpoint. --- # Oil slick detection > Detect oil slicks on the sea surface from Sentinel-1 SAR imagery. Source: https://docs.spcsft.com/api-reference/analyze/oilslick **POST** `/api/v1/analyze/oilslick` Detect oil slicks (spills) on the sea surface. Each detected polygon is returned with a stable ID. | | | | ------------------- | --------------------------------- | | Supported satellite | Sentinel-1 | | Typical runtime | \~50 minutes | | Input patterns | `scene_id`, or `polygon` + `date` | ## Request [#request] ### Pattern A — Scene ID [#pattern-a--scene-id] > **Note: Only GRD scenes are supported** > > SateAIs requires **Sentinel-1 GRD** (Ground Range Detected) products. Find scene IDs on [ASF Search](https://search.asf.alaska.edu/#/) with **File Type = GRD**. Non-GRD scene IDs are accepted at submission, but the job then fails with `SCENE_NOT_FOUND` — check the job status rather than expecting a synchronous error. ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/oilslick \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "scene_id": "S1A_IW_GRDH_1SDV_20260312T150000_20260312T150025_054876_069DEF_5678" }' ``` ### Pattern B — Polygon + date [#pattern-b--polygon--date] Same parameter semantics as the [ship endpoint](https://docs.spcsft.com/api-reference/analyze/ship.md). SateAIs auto-selects a Sentinel-1 **GRD** scene. ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/oilslick \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "polygon": "POLYGON((48.262939 28.969701, 49.086914 28.969701, 49.086914 29.496988, 48.262939 29.496988, 48.262939 28.969701))", "date": "2026-03-12", "date_direction": "nearest" }' ``` ## Parameters [#parameters] Identical to [Ship detection](https://docs.spcsft.com/api-reference/analyze/ship.md). ## Result GeoJSON [#result-geojson] Each feature's `properties` includes a stable `id`: ```json { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "Polygon", "coordinates": [[[2.627, 61.216], [2.627, 61.215], [2.624, 61.215], [2.624, 61.216], [2.627, 61.216]]] }, "properties": { "id": 1 } } ] } ``` ## Next steps [#next-steps] - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Poll for completion. - [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Download the GeoJSON. - [Ship detection](https://docs.spcsft.com/api-reference/analyze/ship.md): Similar async detection for ships. - [Errors](https://docs.spcsft.com/errors.md): Error codes for this endpoint. --- # Preview a request > Check the analysed area and credit cost before submitting a job. Source: https://docs.spcsft.com/api-reference/analyze/preview **POST** `/api/v1/analyze/{endpoint}/preview` Preview what a detection request would do **before submitting it**: which part of your AOI is expected to be analysed, and how many credits it will consume. No job is created and no credits are charged. The request body is **identical to the submission body** for the same endpoint. Build one request object, preview it, then submit the same object unchanged. The SDKs expose this as `client.preview.*` — the same methods as `client.analyze.*` — in [Python](https://docs.spcsft.com/sdk/python/reference.md#preview-methods) and [JavaScript](https://docs.spcsft.com/sdk/javascript/reference.md#preview-methods), and the Python CLI as `sateais preview `. ## Request [#request] ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/newbuilding/preview \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "polygon": "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", "date_start": "2024-01-01", "date_end": "2026-03-01" }' ``` The `{endpoint}` path parameter accepts the same detection types as submission: `ship`, `oilslick`, `newbuilding`, `disappearbuilding`, `timeseries`. Parameters, required fields, and constraints are those of the corresponding [detection endpoint](https://docs.spcsft.com/api-reference/analyze.md). ## Response [#response] ```json { "endpoint_id": "newbuilding", "area_sqkm": 80.65, "coverage": { "method": "estimated", "requested_area_sqkm": 103.4, "ratio": 0.78, "polygon": "POLYGON((139.7 35.6, ...))" }, "credits": { "estimated": 1.03, "balance": 480.0, "sufficient": true }, "warnings": [ { "code": "LOW_AOI_COVERAGE", "message": "Scenes cover only 78% of the requested area." } ] } ``` | Field | Type | Description | | -------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `endpoint_id` | `string` | Echo of the requested detection type. | | `area_sqkm` | `number \| null` | Area the estimate is based on (km²). When `coverage` is present this is the area expected to be analysed; otherwise it is the area of the polygon you submitted. | | `coverage` | `object \| null` | Expected analysed portion of your AOI, in the same structure as [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md#coverage). Before submission it is always `method: "estimated"`. Absent when it cannot be predicted — **absent means unknown, not 100%**. | | `credits.estimated` | `number \| null` | Expected credit consumption. `null` means it cannot be determined before submission, **not** that the job is free — the reason arrives as a `CREDITS_NOT_ESTIMABLE` warning. | | `credits.balance` | `number` | Your current credit balance. | | `credits.sufficient` | `boolean \| null` | Whether `balance` covers `estimated`. `null` when `estimated` is `null`. | | `warnings` | `array` | The warnings that can be determined before submission: `LOW_AOI_COVERAGE` and `CREDITS_NOT_ESTIMABLE`. The remaining scene-selection warnings only appear on the job itself. See [Warnings](https://docs.spcsft.com/errors.md#warnings). | ### When can credits be estimated? [#when-can-credits-be-estimated] | Input pattern | Credits determined by | `credits.estimated` | | ----------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------- | | `polygon` — including `ship` / `oilslick` with `polygon` + `date` | Your polygon | Returned | | `scene_id` | The full scene footprint, not known until the scene is resolved | `null`, with a `CREDITS_NOT_ESTIMABLE` warning | Actual consumption can be **lower** than the estimate: the estimate is based on your requested area, while the final charge excludes portions without usable data. ### Coverage prediction [#coverage-prediction] The preview runs the **same scene selection as the actual job**, over the same imagery, and reports the coverage of the scene that will be used. The predicted `ratio` therefore matches the post-job `measured` value up to the no-data margin inside the scene. | Endpoints | Scenes the prediction is based on | | ------------------------------------------------ | --------------------------------------------------------------------- | | `newbuilding`, `disappearbuilding`, `timeseries` | The pair or series the job will compare, over `date_start`–`date_end` | | `ship`, `oilslick` | The single scene nearest your `date`, honouring `date_direction` | These two groups are served by different imagery products, so a figure predicted from the wrong one would describe a different scene. Coverage is always derived from the imagery the job itself will use. ```json { "endpoint_id": "ship", "area_sqkm": 225.35, "coverage": { "method": "estimated", "requested_area_sqkm": 225.35, "ratio": 1.0, "polygon": "POLYGON((139.717244 35.567628, ...))" }, "credits": { "estimated": 2.25, "balance": 480.0, "sufficient": true }, "warnings": [] } ``` If the scene search fails or times out, or no scene matches your `date_direction`, the response omits `coverage` and still returns `credits` — a rough answer beats no answer. ## Validation [#validation] The preview runs the **same validation functions as submission, in the same order**. Invalid bodies, malformed WKT, unsupported input patterns, area or date range over the limit, and missing permissions return the same status codes as the detection endpoint. Three things are checked only at submission, so a successful preview does not guarantee the job starts: * An insufficient balance — the preview returns `200` with `"sufficient": false` instead of `402`, because finding out how much you are short is part of what a preview is for. * Whether a `polygon_id` or `tif_id` exists and is ready (`404` / `409`). * The concurrent-job limit (`429`), which depends on what is running at that moment. ## Next steps [#next-steps] - [Analysis APIs](https://docs.spcsft.com/api-reference/analyze.md): Submit the previewed request. - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Compare the prediction with the measured coverage. - [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md): How AOIs are clipped to scenes. - [Errors](https://docs.spcsft.com/errors.md): Warning codes returned by the preview. --- # Ship detection > Detect ships from Sentinel-1 SAR imagery. Source: https://docs.spcsft.com/api-reference/analyze/ship **POST** `/api/v1/analyze/ship` Detect vessels at sea from Sentinel-1 SAR scenes. Returns a GeoJSON `FeatureCollection` of detected ships with their bounding geometry. | | | | ------------------- | --------------------------------- | | Supported satellite | Sentinel-1 | | Typical runtime | \~15 minutes | | Input patterns | `scene_id`, or `polygon` + `date` | ## Request [#request] ### Pattern A — Scene ID (full scene) [#pattern-a--scene-id-full-scene] Processes an entire Sentinel-1 scene. Use this when you know the scene ID. > **Note: Only GRD scenes are supported** > > SateAIs requires **Sentinel-1 GRD** (Ground Range Detected) products. Find scene IDs on [ASF Search](https://search.asf.alaska.edu/#/) with **File Type = GRD**. Non-GRD scene IDs are accepted at submission, but the job then fails with `SCENE_NOT_FOUND` — check the job status rather than expecting a synchronous error. ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/ship \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "scene_id": "S1A_IW_GRDH_1SDV_20260310T022000_20260310T022025_054321_068ABC_4321" }' ``` ### Pattern B — Polygon + date (auto scene selection) [#pattern-b--polygon--date-auto-scene-selection] SateAIs picks the Sentinel-1 **GRD** scene closest to `date` within ±14 days, then clips to the polygon. Reduces credit usage for localized analysis. ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/ship \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "polygon": "POLYGON((55.541382 26.743158, 56.186829 26.743158, 56.186829 27.054234, 55.541382 27.054234, 55.541382 26.743158))", "date": "2026-03-10", "date_direction": "nearest", "orbit_direction": "ascending" }' ``` ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | -------------------------------------- | ----------- | ------------------------------------------------------------ | | `satellite_id` | `string` | Yes | Must be `"sentinel-1"`. | | `scene_id` | `string` | Conditional | Required when `polygon` is not provided. | | `polygon` | `string` (WKT) | Conditional | Required when `scene_id` is not provided. EPSG:4326. | | `date` | `string` (`YYYY-MM-DD`) | Conditional | Required with `polygon`. Reference date for scene selection. | | `date_direction` | `"before"` \| `"after"` \| `"nearest"` | No | Defaults to `"nearest"`. | | `orbit_direction` | `"ascending"` \| `"descending"` | No | Filter scenes by orbit direction. | ## Response [#response] ```json { "job_id": "351e635d-7c25-4ae8-a2a5-60c01a6f434c", "status": "pending", "created_at": "2026-03-10T02:20:00Z", "completed_at": null, "result_path": null, "error": null } ``` Poll `GET /api/v1/jobs/{job_id}` until `status` becomes `completed`, then download the GeoJSON result. See [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md). ## Next steps [#next-steps] - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Poll for completion. - [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Download the GeoJSON. - [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md): WKT polygon format reference. - [Errors](https://docs.spcsft.com/errors.md): Error codes for this endpoint. --- # Time-series change > Analyze change over time by comparing all Sentinel-1 scenes in a period. Source: https://docs.spcsft.com/api-reference/analyze/timeseries **POST** `/api/v1/analyze/timeseries` Analyze changes in an area of interest across all Sentinel-1 scenes sharing a path/frame within a date range. | | | | ------------------- | --------------------------------------------------------- | | Supported satellite | Sentinel-1 (RTC — terrain-corrected, selected by the API) | | Typical runtime | Depends on scene count | | Input pattern | `polygon` + `date_start` + `date_end` only | ## Constraints [#constraints] | Constraint | Value | | ------------ | ---------------------------- | | Polygon area | ≤ **50 km²** (≈ 7 km × 7 km) | | Date range | ≤ **3 years** | Exceeding either constraint returns `400 VALIDATION_ERROR` **before any credits are consumed**. ## Request [#request] ```bash curl -X POST https://api.spcsft.com/api/v1/analyze/timeseries \ -H "Authorization: Bearer sk_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "satellite_id": "sentinel-1", "polygon": "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", "date_start": "2023-01-01", "date_end": "2026-03-01", "orbit_direction": "ascending" }' ``` ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ------------------------------- | -------- | ----------------------------------------- | | `satellite_id` | `string` | Yes | Must be `"sentinel-1"`. | | `polygon` | `string` (WKT) | Yes | AOI polygon in EPSG:4326. ≤ 50 km². | | `date_start` | `string` (`YYYY-MM-DD`) | Yes | Start of the period. | | `date_end` | `string` (`YYYY-MM-DD`) | Yes | End of the period. ≤ 3 years after start. | | `orbit_direction` | `"ascending"` \| `"descending"` | No | Filter by orbit direction. | ## Next steps [#next-steps] - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Poll for completion. - [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Download the GeoJSON. - [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md): Handling the 50 km² limit. - [Errors](https://docs.spcsft.com/errors.md): Error codes for this endpoint. --- # Job Management > Endpoints for polling detection jobs and downloading GeoJSON results. Source: https://docs.spcsft.com/api-reference/jobs - [List jobs](https://docs.spcsft.com/api-reference/jobs/list-jobs.md): List your recent jobs, filtered by status or endpoint. - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Poll a job until completion. - [Get job result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Fetch the GeoJSON FeatureCollection for a completed job. --- # Get job status > Retrieve the status and metadata of a detection job. Source: https://docs.spcsft.com/api-reference/jobs/get-job **GET** `/api/v1/jobs/{job_id}` Retrieve the current status of a detection job. Poll this endpoint until `status` becomes `completed` or `failed`. ## Request [#request] ```bash curl https://api.spcsft.com/api/v1/jobs/351e635d-7c25-4ae8-a2a5-60c01a6f434c \ -H "Authorization: Bearer sk_live_xxxxx" ``` ## Path parameters [#path-parameters] | Parameter | Type | Description | | --------- | --------------- | ------------------------------------- | | `job_id` | `string` (UUID) | Returned from the detection endpoint. | ## Response [#response] ### Completed job [#completed-job] ```json { "job_id": "351e635d-7c25-4ae8-a2a5-60c01a6f434c", "endpoint_id": "newbuilding", "satellite_id": "sentinel-1", "status": "completed", "created_at": "2026-03-12T15:00:00Z", "completed_at": "2026-03-12T15:32:10Z", "result_path": "/api/v1/jobs/351e635d-7c25-4ae8-a2a5-60c01a6f434c/result.geojson", "error_code": null, "error_message": null, "error": null, "area_sqkm": 80.7, "coverage": { "method": "measured", "requested_area_sqkm": 103.4, "ratio": 0.78, "polygon": "POLYGON((139.7 35.6, ...))" }, "scene_selection": { "first_month": "2024-01", "last_month": "2026-02" }, "warnings": [ { "code": "LOW_AOI_COVERAGE", "message": "Scenes cover only 78% of the requested area." } ] } ``` ### Failed job [#failed-job] ```json { "job_id": "351e635d-7c25-4ae8-a2a5-60c01a6f434c", "endpoint_id": "timeseries", "satellite_id": "sentinel-1", "status": "failed", "created_at": "2026-03-12T15:00:00Z", "completed_at": "2026-03-12T15:31:15Z", "result_path": null, "error_code": "VALIDATION_ERROR", "error_message": "Polygon area (52.6 km²) exceeds 50 km² limit for endpoint 'timeseries'", "error": "VALIDATION_ERROR", "area_sqkm": null, "coverage": null, "scene_selection": null, "warnings": [] } ``` ## Response fields [#response-fields] | Field | Type | Description | | ----------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `job_id` | `string` | Job UUID. | | `endpoint_id` | `string \| null` | Detection type (`ship`, `oilslick`, `newbuilding`, `disappearbuilding`, `timeseries`, `idlefarm`). `null` for jobs submitted before this field existed. | | `satellite_id` | `string \| null` | Satellite used for the analysis. | | `status` | `string` | `pending` / `processing` / `completed` / `failed` | | `created_at` | `string` | ISO 8601 timestamp. | | `completed_at` | `string \| null` | ISO 8601 timestamp. `null` while pending/processing. | | `result_path` | `string \| null` | GeoJSON download path. `null` unless completed. | | `error_code` | `string \| null` | Machine-readable error code. See [Errors](https://docs.spcsft.com/errors.md). | | `error_message` | `string \| null` | Human-readable error detail. Fixed message for 5xx errors. | | `error` | `string \| null` | **Deprecated.** Same as `error_code`. Use `error_code` instead. | | `area_sqkm` | `number \| null` | Area in km² that credits were charged on. For `newbuilding` / `disappearbuilding` / `timeseries` it is the actually analysed area, with no-data excluded. | | `coverage` | `object \| null` | How much of your requested polygon was actually analysed. See [Coverage](#coverage). | | `scene_selection` | `object \| null` | When the imagery used was captured. `null` when you supplied `scene_id`, or for jobs submitted before this field existed. | | `warnings` | `array` | Non-fatal notices about the selected imagery. Empty when the request was satisfied as asked. | ## Coverage [#coverage] The polygon you request and the area actually analysed are rarely identical: the selected scene may not cover the whole AOI, and portions without usable data are excluded. `coverage` quantifies that difference, so you can tell **"nothing detected"** apart from **"not analysed"**. | Field | Type | Description | | ------------------------------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `coverage.method` | `"measured"` \| `"estimated"` | How the value was obtained — see below. | | `coverage.requested_area_sqkm` | `number` | Area of the polygon you submitted (km²). | | `coverage.ratio` | `number` | Fraction of the requested area that was analysed, `0.0`–`1.0`, rounded **down** to two decimals. | | `coverage.polygon` | `string \| null` | WKT of the analysed extent, in the same format as the request `polygon`. In job responses it is present only for `measured` coverage; a [preview](https://docs.spcsft.com/api-reference/analyze/preview.md) carries the predicted extent instead. | | `method` | Derived from | Endpoints | | ----------- | ------------------------------------------------------------------ | ------------------------------------------------ | | `measured` | The actual data boundary after excluding no-data pixels | `newbuilding`, `disappearbuilding`, `timeseries` | | `estimated` | The intersection of your polygon with the selected scene footprint | `ship`, `oilslick` (polygon input) | `coverage` is absent for inputs where the analysed fraction cannot be determined, such as `scene_id` input. **Treat a missing `coverage` as unknown — do not display it as 100%.** > **Note** > > `coverage.polygon` is simplified to fit within the request size limit, so you can resubmit it — or the missing remainder — directly as a new request's `polygon`. When no-data splits the analysed area it is a `MULTIPOLYGON`; split it into individual polygons before resubmitting. ## Scene selection [#scene-selection] When you submit a `polygon`, the API picks the Sentinel-1 imagery for you. `scene_selection` tells you **when the imagery used was captured**, so you can confirm it matches the period you asked for. | Field | Type | Description | | ----------------------------- | ---------------- | --------------------------------------------------------- | | `scene_selection.first_month` | `string \| null` | Observation month of the earliest scene used (`YYYY-MM`). | | `scene_selection.last_month` | `string \| null` | Observation month of the latest scene used (`YYYY-MM`). | Compare `first_month` / `last_month` against the `date_start` / `date_end` you requested. A gap means suitable imagery was not available across the whole period — `warnings` explains why. > **Note** > > Observation dates are reported to month precision only. Scene identifiers and exact acquisition timestamps are not returned. ### Warnings [#warnings] A job with warnings still succeeds: `status` is `completed` and `error_code` is `null`. Warnings tell you whether to trust the result as-is. ```json { "status": "completed", "error_code": null, "scene_selection": { "first_month": "2021-01", "last_month": "2021-12" }, "warnings": [ { "code": "PERIOD_PARTIALLY_COVERED", "message": "Observations cover only 30% of the requested period (2021-01-01 to 2023-12-31)." } ] } ``` Each entry has a machine-readable `code` and a human-readable `message`. See [Warnings](https://docs.spcsft.com/errors.md#warnings) for the full list. ## Status values [#status-values] | Status | Description | | ------------ | ---------------------------------------------------- | | `pending` | Queued, not yet started. | | `processing` | Currently running. | | `completed` | Done. Result available for 30 days. | | `failed` | Failed. See `error` for details. No credits charged. | ## Polling guidance [#polling-guidance] Poll about **once per minute**. See [How it works](https://docs.spcsft.com/concepts/how-it-works.md#recommended-polling-interval) for per-endpoint timing recommendations. ## Next steps [#next-steps] - [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Download the GeoJSON. - [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): Check coverage and cost before submitting. - [Errors](https://docs.spcsft.com/errors.md): Handle failures. --- # Get result > Download the GeoJSON result of a completed detection job. Source: https://docs.spcsft.com/api-reference/jobs/get-result **GET** `/api/v1/jobs/{job_id}/result.geojson` Download the GeoJSON `FeatureCollection` produced by a completed job. The response is a **`302` redirect** to a pre-signed S3 URL. Follow redirects (most HTTP clients do automatically). ## Request [#request] ```bash curl -L https://api.spcsft.com/api/v1/jobs/351e635d-7c25-4ae8-a2a5-60c01a6f434c/result.geojson \ -H "Authorization: Bearer sk_live_xxxxx" \ -o result.geojson ``` The `-L` flag tells `curl` to follow the redirect. ## Response [#response] A GeoJSON `FeatureCollection` conforming to [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946). Coordinates are WGS84 longitude/latitude. ```json { "type": "FeatureCollection", "bbox": [139.65, 35.67, 139.66, 35.68], "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "Polygon", "coordinates": [[[139.65, 35.67], [139.66, 35.67], [139.66, 35.68], [139.65, 35.68], [139.65, 35.67]]] }, "properties": { "area_sqm": 2480.5, "confidence": 0.8734 } } ] } ``` | Member | Description | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `bbox` | Bounding box covering all detections, as `[minx, miny, maxx, maxy]`. **Omitted when there are no detections.** | | `features[].id` | Sequential identifier starting at `1`. Sits on the Feature itself, not inside `properties`. | | `features[].properties.area_sqm` | Polygon area in **square metres**, computed geodesically on the WGS84 ellipsoid so it is unaffected by projection distortion. Holes are subtracted. | | `features[].properties.confidence` | Median of the model's probability values over the pixels the polygon covers, or `null`. See below. | ### Confidence [#confidence] `confidence` is **always present** on every Feature, so `confidence === null` is the single check for "this detection type carries no confidence". It is never absent. | Endpoint | `confidence` | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `newbuilding`, `disappearbuilding` | Median probability over the polygon | | `ship` | `null` — detection uses CFAR (a brightness ratio against the local background) and produces no probability | | `oilslick` | `null` — the detector keeps only very high probability pixels, so every polygon would report the same value | **Do not compare `confidence` across endpoints, and do not read it as an absolute likelihood.** Each model is tuned independently, so the same number means different things for different detection types — a value that is unremarkable for one endpoint may be exceptional for another. Use it to rank and filter detections **within a single job**. Values near zero do not occur: only pixels the model scored above its detection threshold become part of a polygon at all, so the lowest `confidence` you can see is bounded by that threshold rather than by 0. A polygon whose pixels could not be read reports `null` rather than a substituted value. > **Note** > > There is no `crs` member. RFC 7946 fixes coordinates to WGS84 and removed `crs` from the specification.The detection type is not part of the result. Read `endpoint_id` from [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md) — the download filename is also `{endpoint_id}_{job_id}.geojson`. When no detections are found: ```json { "type": "FeatureCollection", "features": [] } ``` ### Endpoints with a different shape [#endpoints-with-a-different-shape] | Endpoint | Shape | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `timeseries` | One Feature per grid cell, carrying `deviation` (deviation from the long-term moving average, divided by its standard deviation), `changePointDates`, styling keys, and time-series chart data. | | `idlefarm` | Returns a `.zip` archive from `/api/v1/jobs/{job_id}/result.zip`, not GeoJSON. | ## Retention [#retention] Results are available for **30 days after completion**. Afterwards the endpoint returns: ```http HTTP/1.1 410 Gone ``` ```json { "error": { "code": "GONE", "message": "Result has been deleted after the 30-day retention period" } } ``` Job metadata (status, timestamps) remains accessible via [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md) indefinitely. Clients should detect `error.code === "GONE"` and surface a dedicated message. > **Warning** > > Download and archive results you need to retain beyond 30 days. There is currently no option to extend retention per job. ## Next steps [#next-steps] - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Check completion before downloading. - [Errors](https://docs.spcsft.com/errors.md): Handle 410 and other failures. --- # List jobs > Retrieve your most recent detection jobs, optionally filtered by status and endpoint. Source: https://docs.spcsft.com/api-reference/jobs/list-jobs **GET** `/api/v1/jobs` List your **most recent detection jobs**. Filter by `status` and `endpoint_id`, adjust the page size with `limit`. Always sorted newest first. The endpoint is designed for building "Recent jobs" panels in dashboards, CLI listings, and QGIS plugin history views without having to remember `job_id`s. ## Request [#request] ```bash curl "https://api.spcsft.com/api/v1/jobs?status=completed&limit=10" \ -H "Authorization: Bearer sk_live_xxxxx" ``` ## Query parameters [#query-parameters] | Parameter | Type | Default | Description | | ------------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `status` | `string` | (all) | Filter by status. Comma-separated for multiple values (`pending,processing`). Allowed: `pending` / `processing` / `completed` / `failed`. | | `endpoint_id` | `string` | (all) | Filter by detection endpoint. Comma-separated for multiple (`ship,oilslick`). Allowed: `ship` / `oilslick` / `newbuilding` / `disappearbuilding` / `timeseries`. | | `limit` | `integer` | `30` | Maximum number of jobs to return. Must be between `1` and `30`. | ### Filter semantics [#filter-semantics] * Values are **strict lowercase**. `Pending` returns `400`. * Duplicate values are deduplicated (`?status=pending,pending` behaves as `?status=pending`). * Empty segments are ignored (`?status=,,pending,,` behaves as `?status=pending`). * Repeated keys (`?status=pending&status=processing`) are combined, and behave the same as `?status=pending,processing`. * Unknown query parameters are silently ignored. ## Response [#response] ```json { "jobs": [ { "job_id": "351e635d-7c25-4ae8-a2a5-60c01a6f434c", "endpoint_id": "ship", "satellite_id": "sentinel-1", "status": "completed", "created_at": "2026-06-29T10:00:00Z", "completed_at": "2026-06-29T10:30:00Z", "credits_used": 120.0, "area_sqkm": 42.5, "lat": 35.68, "lon": 139.76, "request_params": { "scene_id": "S1A_IW_GRDH_1SDV_...", "polygon": "POLYGON((...))", "date": "2026-06-29", "date_direction": "nearest" }, "result_path": "/api/v1/jobs/351e635d-7c25-4ae8-a2a5-60c01a6f434c/result.geojson", "error_code": null, "error_message": null } ] } ``` ### Response headers [#response-headers] | Header | Value | Reason | | --------------- | ---------------------------------------------- | -------------------------------------------------------------- | | `Cache-Control` | `private, no-store, no-cache, must-revalidate` | Prevents intermediate proxies from caching per-user job lists. | | `Vary` | `Authorization` | Ensures cache keys stay separated per API key. | ## Response fields [#response-fields] | Field | Type | Description | | ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `jobs` | `array` | Job items, newest first. Empty array if you have no jobs. | | `jobs[].job_id` | `string` (UUID) | Job identifier. | | `jobs[].endpoint_id` | `string` | Detection endpoint (`ship`, `newbuilding`, ...). | | `jobs[].satellite_id` | `string \| null` | Satellite (`sentinel-1`). May be `null` for endpoints that don't tie to a single satellite. | | `jobs[].status` | `string` | `pending` / `processing` / `completed` / `failed`. | | `jobs[].created_at` | `string` | ISO 8601 UTC (always with `Z` suffix). | | `jobs[].completed_at` | `string \| null` | ISO 8601 UTC. `null` while pending/processing. | | `jobs[].credits_used` | `number \| null` | Credits charged for the job. `null` while pending. | | `jobs[].area_sqkm` | `number \| null` | Processed area. `null` while pending. | | `jobs[].lat` | `number \| null` | Latitude of the scene / AOI centroid. `null` while pending. | | `jobs[].lon` | `number \| null` | Longitude of the scene / AOI centroid. `null` while pending. | | `jobs[].request_params` | `object \| null` | The parameters you passed to `POST /api/v1/analyze/{endpoint}`. Shape depends on `endpoint_id`; useful for redrawing the polygon or re-running with tweaks. Treat every string value as **untrusted** — escape before inserting into HTML. | | `jobs[].result_path` | `string \| null` | Result download path (`.geojson`). `null` unless `status=completed`. | | `jobs[].error_code` | `string \| null` | Machine-readable error code. See [Errors](https://docs.spcsft.com/errors.md). | | `jobs[].error_message` | `string \| null` | Human-readable error detail. **5xx errors return a fixed generic message** — inspect `error_code` for the actual category. | ## Ordering and paging [#ordering-and-paging] * Sort is fixed: `created_at DESC, job_id DESC`. The secondary sort by `job_id` guarantees a deterministic order when multiple jobs share the same `created_at`. * There is **no cursor / offset in v1**. Requesting `?limit=30` always returns the 30 newest jobs. If you need older history, request individual jobs via [`GET /api/v1/jobs/{job_id}`](https://docs.spcsft.com/api-reference/jobs/get-job.md) — you must keep the `job_id` yourself. ## Errors [#errors] | Status | Code | When | | ------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `400` | `VALIDATION_ERROR` | Invalid `status` / `endpoint_id` value, non-integer / out-of-range `limit`, or a query parameter value longer than 128 characters. | | `401` | `UNAUTHORIZED` | Missing or invalid `Authorization` header. | See [Errors](https://docs.spcsft.com/errors.md) for the full error contract. ## Examples [#examples] **Most recent 30 jobs** ```bash curl https://api.spcsft.com/api/v1/jobs \ -H "Authorization: Bearer sk_live_xxxxx" ``` **Only the latest completed job** ```bash curl "https://api.spcsft.com/api/v1/jobs?status=completed&limit=1" \ -H "Authorization: Bearer sk_live_xxxxx" ``` **In-flight ship / oilslick jobs** ```bash curl "https://api.spcsft.com/api/v1/jobs?status=pending,processing&endpoint_id=ship,oilslick" \ -H "Authorization: Bearer sk_live_xxxxx" ``` ## Next steps [#next-steps] - [Get job status](https://docs.spcsft.com/api-reference/jobs/get-job.md): Fetch a single job by ID. - [Get result](https://docs.spcsft.com/api-reference/jobs/get-result.md): Download the GeoJSON. --- # Pricing > SateAIs plans and credits. Source: https://docs.spcsft.com/pricing - [Plans](https://docs.spcsft.com/pricing/plans.md): Free plan during beta. Pro / Enterprise planned after beta. --- # Plans > SateAIs API pricing plans. Start free during the beta with 500 credits per month. Source: https://docs.spcsft.com/pricing/plans SateAIs API is currently in **beta**. During the beta, all new accounts are enrolled in the **Free** plan. ## Free plan (Beta) [#free-plan-beta] | | | | ------------------- | ----------------------------------------------------------------------- | | **Monthly credits** | 500 | | **Price** | $0 | | **Analysis APIs** | Ship · Oil slick · New building · Disappeared building · Time-series | | **Concurrent jobs** | 5 per API key | | **Support** | Email ([console-support@spcsft.com](mailto:console-support@spcsft.com)) | * **500 credits per month** — enough to analyze up to 50,000 km² of satellite imagery * Monthly credit reset on the first of each calendar month (UTC) * Unused credits do **not** roll over ## Pro & Enterprise [#pro--enterprise] Pro and Enterprise plans are planned for after the beta. If you need higher volume during the beta, [contact sales](mailto:console-support@spcsft.com). ## Next steps [#next-steps] - [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md): Concurrency and throughput limits. --- # MCP (Model Context Protocol) > Connect AI agents like Claude and ChatGPT to SateAIs via the Model Context Protocol. Source: https://docs.spcsft.com/mcp The SateAIs MCP server lets AI agents call satellite detection tools directly. Point any MCP-compatible client at the shared endpoint and sign in with your SpaceShift account — no API key handling required. ## How it works [#how-it-works] * **Endpoint**: `https://mcp.spcsft.com/mcp` (same URL for every user) * **Authentication**: OAuth 2.1 — your client opens a browser window where you sign in to SpaceShift and approve access * **Transport**: Streamable HTTP (stateless, JSON) > **Note** > > A [SpaceShift Console](https://console.spcsft.com) account with at least one active API key is required. MCP jobs run against your account, so credits, rate limits, and endpoint permissions follow the same rules as the REST API. ## Connect your client [#connect-your-client] Claude.ai (Pro / Team / Enterprise) supports remote MCP servers natively — no local bridge required. 1. Open Claude.ai and go to **Settings → Customize → Connectors**. 2. Click **Add custom connector** and enter the endpoint URL: ``` https://mcp.spcsft.com/mcp ``` 3. Click **Connect**. A browser window opens — sign in with your SpaceShift account and approve the access request on the consent screen. 4. The `sateais` connector becomes available in any new chat — toggle it on from the conversation's tools menu. > **Note** > > Custom connectors require a Claude.ai paid plan. Free accounts cannot add custom MCP servers — use Claude Desktop instead. Edit the Claude Desktop configuration file and add a `sateais` entry under `mcpServers`: * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` ```json { "mcpServers": { "sateais": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.spcsft.com/mcp"] } } } ``` Restart Claude Desktop. On first launch, `mcp-remote` opens a browser window — sign in with your SpaceShift account and approve access. The `sateais` server then appears under **Settings → Developer → MCP Servers**, and the tools become available in any chat. > **Note** > > `mcp-remote` is a stdio↔HTTP bridge — Claude Desktop launches it locally and it forwards requests to `mcp.spcsft.com`, handling the OAuth flow and token refresh automatically. Node.js 18+ is required. ChatGPT supports MCP via **Custom connectors** (Developer mode, currently rolling out to Plus / Pro / Enterprise plans). 1. Open ChatGPT and go to **Settings → Connectors → Advanced → Developer mode**. 2. Click **Add custom connector** and set the connector URL: ``` https://mcp.spcsft.com/mcp ``` 3. ChatGPT detects the OAuth configuration automatically. Complete the sign-in and consent flow in the browser window that opens. 4. Enable the connector for the conversation where you want to use it. > **Note** > > Custom connectors require ChatGPT Developer mode, which may not be available on all plans. See [OpenAI's MCP documentation](https://platform.openai.com/docs/mcp) for the latest availability. ## Registries and directories [#registries-and-directories] SateAIs is published in the official [MCP Registry](https://registry.modelcontextprotocol.io) as `com.spcsft/sateais`, and is listed on community directories such as [Smithery](https://smithery.ai), [Glama](https://glama.ai/mcp/servers), and [PulseMCP](https://www.pulsemcp.com). Clients and tools that support registry-based discovery can find the server by that name. However you discover the server, the connection details are the same: the endpoint is `https://mcp.spcsft.com/mcp` and authorization uses OAuth sign-in, as described in [Connect your client](#connect-your-client). ## Available tools [#available-tools] The MCP server exposes the same detection capabilities as the REST API: | Tool | Description | | --------------------------- | ------------------------------------------------------ | | `analyze_ship` | Detect vessels in Sentinel-1 SAR imagery. | | `analyze_oilslick` | Identify oil spills on the sea surface. | | `analyze_newbuilding` | Find newly constructed structures between two periods. | | `analyze_disappearbuilding` | Identify buildings missing between two periods. | | `analyze_timeseries` | Detect surface change between observations. | | `check_job_status` | Poll the status of a submitted job. | | `list_jobs` | List your recent jobs, newest first (up to 30). | | `get_job_result` | Fetch the GeoJSON result of a completed job. | | `open_polygon_draw` | Open a polygon-drawing widget for AOI definition. | Credits, rate limits, and endpoint permissions follow the same rules as the REST API — see [Authentication](https://docs.spcsft.com/authentication.md) and [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md). ## Revoking access [#revoking-access] To disconnect an AI agent from your account: * Remove the connector from the client (Claude / ChatGPT settings), and * Revoke the underlying API key in the [SateAIs API Console](https://console.spcsft.com/api-keys) if you want to invalidate all access immediately. ## Legacy: API-key connection URLs (deprecated) [#legacy-api-key-connection-urls-deprecated] > **Warning: Deprecated** > > Connection URLs with an embedded API key (`?sateaisApiKey=sk_live_xxxxx`) are deprecated in favor of OAuth. They keep working during a migration period — responses include a `Sunset` header with the removal date — but new setups should use OAuth. Because a legacy URL embeds your API key, treat it like a password: never commit it to a public repository, and rotate the key immediately if the URL leaks. If you still use a legacy URL, migrate by replacing it with the plain endpoint `https://mcp.spcsft.com/mcp` in your client configuration and completing the OAuth sign-in once. ## Troubleshooting [#troubleshooting] | Symptom | Likely cause | Fix | | -------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `401 Unauthorized` from the MCP server | OAuth authorization expired or revoked | Reconnect the connector and complete the sign-in flow again. | | Consent screen never appears | Pop-up blocked, or not signed in to the Console | Allow pop-ups for your client, sign in at [console.spcsft.com](https://console.spcsft.com), then retry. | | `403 Forbidden` on a tool call | Plan lacks permission for the endpoint | Check entitlements in the Console. | | Claude Desktop shows no tools | `mcp-remote` failed to launch | Confirm Node.js 18+ is on `PATH`, then restart Claude Desktop. | | Job stays `pending` for >2 hours | Underlying detection job stalled | Call `check_job_status` directly; contact support if it remains stuck. | ## Next steps [#next-steps] - [Authentication](https://docs.spcsft.com/authentication.md): How API keys are issued and rotated. - [Rate limits](https://docs.spcsft.com/concepts/rate-limits.md): Per-key request and concurrency limits. - [Detection APIs](https://docs.spcsft.com/api-reference/analyze/ship.md): Parameters for the underlying detection endpoints. - [Errors](https://docs.spcsft.com/errors.md): Error codes and retry guidance. --- # SDKs > Official Python and JavaScript/TypeScript SDKs for the SateAIs API. Source: https://docs.spcsft.com/sdk The official SateAIs SDKs wrap the [REST API](https://docs.spcsft.com/api-reference.md) with a typed client, automatic credential resolution, and a built-in polling helper that waits for a job to finish. They share the same method shape across languages, so examples translate one-to-one. ## Choose your language [#choose-your-language] - [Quickstart](https://docs.spcsft.com/sdk/quickstart.md): Submit, poll, and save a result end-to-end in either language. - [Python](https://docs.spcsft.com/sdk/python.md): pip install sateais — typed client, polling helper, and a sateais CLI. - [JavaScript / TypeScript](https://docs.spcsft.com/sdk/javascript.md): npm install @sateais/sdk — zero-dependency, ESM + CommonJS, bundled types. ## Installation at a glance [#installation-at-a-glance] ```bash pip install sateais ``` ```python from sateais import Client client = Client() # reads SATEAIS_API_KEY job = client.analyze.ship(scene_id="S1A_IW_GRDH_...") result = client.jobs.wait(job.job_id) # poll until completed print(len(result["features"]), "ships detected") ``` ```bash npm install @sateais/sdk ``` ```typescript import { Client } from "@sateais/sdk"; const client = new Client(); // reads SATEAIS_API_KEY const job = await client.analyze.ship({ scene_id: "S1A_IW_GRDH_..." }); const result = await client.jobs.wait(job.job_id); // poll until completed console.log(result.features.length, "ships detected"); ``` ## What the SDKs handle for you [#what-the-sdks-handle-for-you] * **Authentication** — resolve the API key from an argument or the `SATEAIS_API_KEY` environment variable. * **Typed methods** — one method per detection type under `client.analyze`, the same set under `client.preview`, plus `client.jobs` for status and results. * **Previewing** — `client.preview.*` returns the area a request would analyse and the credits it would cost, without creating a job or spending credits. * **Polling** — `jobs.wait()` polls until the job completes and returns the GeoJSON, raising a typed error on failure or timeout. * **Errors** — HTTP and job failures are mapped to typed exception classes that mirror the [API error codes](https://docs.spcsft.com/errors.md). > **Note** > > The SDKs are thin wrappers over the REST API. Endpoints, parameters, credit costs, and limits are identical — see the [API Reference](https://docs.spcsft.com/api-reference.md) for the underlying contract. ## Next steps [#next-steps] - [Python SDK](https://docs.spcsft.com/sdk/python.md): Install, configure, and authenticate the Python client. - [JavaScript SDK](https://docs.spcsft.com/sdk/javascript.md): Install, configure, and authenticate the JS/TS client. - [Quickstart](https://docs.spcsft.com/quickstart.md): Submit, poll, and download a result end-to-end. - [API Reference](https://docs.spcsft.com/api-reference.md): The REST contract the SDKs wrap. --- # JavaScript SDK > Install, configure, and authenticate the SateAIs JavaScript/TypeScript client. Source: https://docs.spcsft.com/sdk/javascript The SateAIs JavaScript/TypeScript SDK (`@sateais/sdk`) is a typed, zero-dependency client for the SateAIs API. It ships both ESM and CommonJS builds with bundled type definitions, and runs on Node.js and modern runtimes with a global `fetch`. | | | | ------------ | ----------------------------- | | Package | `@sateais/sdk` (npm) | | Node.js | ≥ 18 | | Dependencies | None (uses global `fetch`) | | Modules | ESM + CommonJS, types bundled | ## Installation [#installation] ```bash npm install @sateais/sdk ``` ```typescript // ESM / TypeScript import { Client } from "@sateais/sdk"; // CommonJS const { Client } = require("@sateais/sdk"); ``` ## Authentication [#authentication] Create an API key in the [SateAIs API Console](https://console.spcsft.com) (see [Authentication](https://docs.spcsft.com/authentication.md)). The client resolves it from the first available source, in order: 1. The `apiKey` option passed to `new Client(...)`. 2. The `SATEAIS_API_KEY` environment variable. ```typescript import { Client } from "@sateais/sdk"; // 1. Explicit option const client = new Client({ apiKey: "sk_live_xxxxx" }); // 2. From SATEAIS_API_KEY (recommended) const client = new Client(); ``` If no key can be resolved, the first request throws an `AuthenticationError`. > **Warning: Keep your key secret** > > Never ship `sk_live_` keys in client-side bundles or commit them to source control. Keep keys server-side, in `SATEAIS_API_KEY`. ## Creating a client [#creating-a-client] ```typescript const client = new Client({ apiKey: process.env.SATEAIS_API_KEY, // falls back to env automatically baseUrl: "https://api.spcsft.com/api/v1", // default timeoutMs: 30_000, // per-request timeout (default 30s) fetch: globalThis.fetch, // custom fetch implementation (optional) }); ``` | Option | Type | Default | Description | | ----------- | -------------- | ------------------------------- | ----------------------------------------------------------------- | | `apiKey` | `string` | `SATEAIS_API_KEY` | API key. Falls back to the environment variable. | | `baseUrl` | `string` | `https://api.spcsft.com/api/v1` | API base URL. Trailing slashes are removed. | | `timeoutMs` | `number` | `30_000` | Per-request timeout in milliseconds (via `AbortController`). | | `fetch` | `typeof fetch` | global `fetch` | Custom fetch implementation for testing or non-standard runtimes. | ## Your first detection [#your-first-detection] ```typescript import { Client } from "@sateais/sdk"; const client = new Client(); // Submit a job const job = await client.analyze.ship({ scene_id: "S1A_IW_GRDH_..." }); console.log(job.job_id, job.status); // "...", "pending" // Poll until completion and fetch the GeoJSON const result = await client.jobs.wait(job.job_id, { intervalMs: 30_000, onPoll: (j) => console.log(j.status), }); console.log(result.features.length, "ships detected"); ``` ## Preview before submitting [#preview-before-submitting] `client.preview.*` mirrors `client.analyze.*` — the same five methods with the same parameters — but creates no job and spends no credits. It returns the area the request is expected to analyse and the credits it would cost, so you can widen the period, split the AOI, or top up before committing: ```typescript const preview = await client.preview.newbuilding({ polygon: "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start: "2026-01-01", date_end: "2026-05-01", }); preview.area_sqkm; // area expected to be analysed (km²) preview.credits.estimated; // expected cost — null means "not determined before submission" preview.credits.balance; // current balance preview.credits.sufficient; // whether the balance covers it — null when estimated is null preview.coverage?.ratio; // fraction of the AOI that will be analysed (0.0–1.0) preview.coverage?.polygon; // WKT of the area that will be analysed for (const warning of preview.warnings) { console.warn(warning.code, warning.message); } ``` A short balance does not throw — that is what `credits.sufficient` is for. `InsufficientCreditsError` is thrown on submission only. Every other check runs the same validation as submission, in the same order, so a preview that resolves means the submission passes validation too. > **Note** > > `credits.estimated` of `null` means the cost is not knowable before submission (a `scene_id` input, for example) — it does not mean the job is free. Likewise `coverage` of `null` means "cannot be predicted", not "100% covered". See [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md). ## Handling errors [#handling-errors] All SDK errors inherit from `SateaisError`. API responses map to typed subclasses that mirror the [API error codes](https://docs.spcsft.com/errors.md): ```typescript import { Client, RateLimitError, JobFailedError, InsufficientCreditsError, } from "@sateais/sdk"; const client = new Client(); try { const job = await client.analyze.timeseries({ polygon: "POLYGON((...))", date_start: "2026-01-01", date_end: "2026-05-01", }); const result = await client.jobs.wait(job.job_id, { timeoutMs: 3_600_000 }); } catch (err) { if (err instanceof InsufficientCreditsError) { console.error("Top up credits in the Console"); } else if (err instanceof RateLimitError) { console.error("Rate limited — back off and retry"); } else if (err instanceof JobFailedError) { console.error("Job failed:", err.errorCode, err.errorMessage); } else { throw err; } } ``` See the [Reference](https://docs.spcsft.com/sdk/javascript/reference.md#errors) for the full error hierarchy. ## Next steps [#next-steps] - [JavaScript reference](https://docs.spcsft.com/sdk/javascript/reference.md): Every method, parameter, return type, and error class. - [Python SDK](https://docs.spcsft.com/sdk/python.md): The same client for Python. - [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): What the preview response tells you, field by field. - [Errors](https://docs.spcsft.com/errors.md): Error codes the SDK maps to error classes. --- # JavaScript reference > Full reference for the SateAIs JavaScript/TypeScript SDK — client, analysis methods, jobs, types, and errors. Source: https://docs.spcsft.com/sdk/javascript/reference Complete reference for the `@sateais/sdk` package. For setup and authentication, see the [JavaScript SDK guide](https://docs.spcsft.com/sdk/javascript.md). ## Client [#client] ```typescript import { Client } from "@sateais/sdk"; const client = new Client(options?: ClientOptions); ``` ### ClientOptions [#clientoptions] | Option | Type | Default | Description | | ----------- | -------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `apiKey` | `string` | `SATEAIS_API_KEY` | API key. Falls back to the environment variable. | | `baseUrl` | `string` | `https://api.spcsft.com/api/v1` | API base URL. Trailing slashes are removed. | | `timeoutMs` | `number` | `30_000` | Per-request timeout in milliseconds. Set to `0` to disable the timeout. | | `fetch` | `typeof fetch` | global `fetch` | Custom fetch implementation. | | `apiClient` | `ApiClient` | — | Inject a custom transport implementing the `ApiClient` interface (`submitAnalysis`, `previewAnalysis`, `getJob`, `getJobResult`). Intended for testing — when set, all HTTP options above are ignored. | **Properties** | Property | Type | Description | | --------- | ----------------- | -------------------------------------------------------------- | | `analyze` | `AnalyzeResource` | Detection job submission methods. | | `preview` | `PreviewResource` | The same methods as `analyze`, previewed instead of submitted. | | `jobs` | `JobsResource` | Job status and result methods. | ## Analysis methods [#analysis-methods] All `client.analyze.*` methods submit a detection job and resolve to a [`JobCreateResponse`](#jobcreateresponse) with `status` `"pending"`. Use [`jobs.wait()`](#jobswait) to await the result. ### analyze.ship / analyze.oilslick [#analyzeship--analyzeoilslick] ```typescript client.analyze.ship(params: SceneAnalyzeParams): Promise ``` `analyze.oilslick(...)` has the same signature. `SceneAnalyzeParams` is a union — pass **either** a `scene_id` or a `polygon` + `date`: ```typescript type SceneAnalyzeParams = SceneIdInput | PolygonDateInput; interface SceneIdInput { satellite_id?: "sentinel-1"; scene_id: string; } interface PolygonDateInput { satellite_id?: "sentinel-1"; polygon: string; // WKT, EPSG:4326 date: string; // "YYYY-MM-DD" date_direction?: "before" | "after" | "nearest"; // default "nearest" orbit_direction?: "ascending" | "descending"; } ``` ```typescript // By scene ID const job = await client.analyze.ship({ scene_id: "S1A_IW_GRDH_..." }); // By polygon + date (auto scene selection) const job = await client.analyze.oilslick({ polygon: "POLYGON((55.5 26.7, 56.1 26.7, 56.1 27.0, 55.5 27.0, 55.5 26.7))", date: "2026-03-10", date_direction: "nearest", }); ``` ### analyze.newbuilding / analyze.disappearbuilding / analyze.timeseries [#analyzenewbuilding--analyzedisappearbuilding--analyzetimeseries] ```typescript client.analyze.newbuilding(params: PolygonPeriodParams): Promise ``` `analyze.disappearbuilding(...)` and `analyze.timeseries(...)` share this signature. ```typescript interface PolygonPeriodParams { satellite_id?: "sentinel-1"; polygon: string; // WKT, EPSG:4326 date_start: string; // "YYYY-MM-DD" date_end: string; // "YYYY-MM-DD" orbit_direction?: "ascending" | "descending"; } ``` ```typescript const job = await client.analyze.timeseries({ polygon: "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start: "2026-01-01", date_end: "2026-05-01", }); ``` > **Note** > > Per-endpoint area caps apply: `newbuilding` / `disappearbuilding` ≤ 30,000 km², `timeseries` ≤ 50 km². Exceeding a cap throws `ValidationError` before any credits are consumed. See [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md). ## Preview methods [#preview-methods] `client.preview.*` has the same five methods as `client.analyze.*`, taking the same parameter types. Each resolves to a [`PreviewResponse`](#previewresponse) instead of a `JobCreateResponse`: no job is created and no credits are consumed. ```typescript client.preview.ship(params: SceneAnalyzeParams): Promise client.preview.oilslick(params: SceneAnalyzeParams): Promise client.preview.newbuilding(params: PolygonPeriodParams): Promise client.preview.disappearbuilding(params: PolygonPeriodParams): Promise client.preview.timeseries(params: PolygonPeriodParams): Promise ``` ```typescript const preview = await client.preview.timeseries({ polygon: "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start: "2026-01-01", date_end: "2026-05-01", }); console.log(preview.credits.estimated, preview.credits.sufficient); ``` Parameter validation is identical to submission — an invalid parameter combination throws `ValidationError` before the request goes out. The one deliberate difference is the balance: a short balance comes back as `credits.sufficient` `false`, never as `InsufficientCreditsError`, since seeing the shortfall is the point of previewing. See [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md) for the underlying contract. ## Job methods [#job-methods] ### jobs.status [#jobsstatus] ```typescript client.jobs.status(jobId: string): Promise ``` Fetches the current state of a job (a single poll). ```typescript const job = await client.jobs.status("351e635d-7c25-4ae8-a2a5-60c01a6f434c"); console.log(job.status); ``` ### jobs.result [#jobsresult] ```typescript client.jobs.result(jobId: string): Promise ``` Returns the GeoJSON `FeatureCollection` for a **completed** job. Throws `NotFoundError` if the job is not complete or the result is past its 30-day retention window. ```typescript const geojson = await client.jobs.result("351e635d-7c25-4ae8-a2a5-60c01a6f434c"); console.log(geojson.type, geojson.features.length); ``` ### jobs.wait [#jobswait] ```typescript client.jobs.wait(jobId: string, options?: WaitOptions): Promise ``` Polls until the job reaches a terminal state, then resolves to the result GeoJSON. ```typescript interface WaitOptions { intervalMs?: number; // default 60_000 timeoutMs?: number; // default Infinity (no limit) onPoll?: (job: JobStatusResponse) => void; } ``` | Option | Type | Default | Description | | ------------ | ---------------------------------- | ---------- | ------------------------------------------------------- | | `intervalMs` | `number` | `60_000` | Milliseconds between status polls. | | `timeoutMs` | `number` | `Infinity` | Maximum time to wait before throwing `JobTimeoutError`. | | `onPoll` | `(job: JobStatusResponse) => void` | — | Called with the latest status after each poll. | Rejects with `JobTimeoutError` if `timeoutMs` elapses first, or `JobFailedError` if the job reaches any status other than `pending`, `processing`, or `completed` — this includes `failed` as well as unrecognized statuses, so polling never loops forever. ```typescript const result = await client.jobs.wait(job.job_id, { intervalMs: 30_000, timeoutMs: 3_600_000, onPoll: (j) => console.log(j.status), }); ``` > **Tip** > > `intervalMs` defaults to 60 s. Runtime varies by endpoint — see [How it works](https://docs.spcsft.com/concepts/how-it-works.md#recommended-polling-interval). ## Types [#types] ### JobCreateResponse [#jobcreateresponse] Returned by all `analyze.*` methods. ```typescript interface JobCreateResponse { job_id: string; status: JobStatus; // "pending" | "processing" | "completed" | "failed" created_at: string; // ISO 8601 completed_at: string | null; result_path: string | null; error: string | null; // null while pending } ``` ### JobStatusResponse [#jobstatusresponse] Returned by `jobs.status()`. ```typescript interface JobStatusResponse { job_id: string; status: JobStatus; created_at: string; completed_at: string | null; result_path: string | null; error_code: string | null; // set when failed error_message: string | null; // set when failed error: string | null; // @deprecated — mirrors error_code } ``` ### GeoJSONResponse [#geojsonresponse] Returned by `jobs.result()` and `jobs.wait()` — a standard GeoJSON `FeatureCollection` in EPSG:4326. ```typescript interface GeoJSONResponse { type: "FeatureCollection"; features: GeoJSONFeature[]; crs?: { type: string; properties: Record }; } ``` ### PreviewResponse [#previewresponse] Returned by all `preview.*` methods. ```typescript interface PreviewResponse { endpoint_id: string; // detection type the preview was run for area_sqkm: number | null; // area expected to be analysed (km²) coverage: Coverage | null; // null = cannot be predicted, NOT "fully covered" credits: PreviewCredits; warnings: SceneWarning[]; // LOW_AOI_COVERAGE, CREDITS_NOT_ESTIMABLE } interface PreviewCredits { estimated: number | null; // null = not determined before submission, NOT free balance: number; // current credit balance sufficient: boolean | null; // null when estimated is null } interface Coverage { method: CoverageMethod; // always "estimated" in a preview requested_area_sqkm: number | null; ratio: number | null; // fraction of the AOI analysed (0.0–1.0) polygon: string | null; // WKT of the area analysed, reusable as a request polygon } type CoverageMethod = "measured" | "estimated"; interface SceneWarning { code: string; message: string; } ``` `credits.estimated` is an upper bound: it is based on the area you requested, while the actual charge excludes portions without usable data. Actual consumption never exceeds it. `method` is `"measured"` only on a finished job, where the analysed area is known exactly — see [Warnings](https://docs.spcsft.com/errors.md#warnings) for the warning codes. ## Errors [#errors] All errors inherit from `SateaisError`. | Error | Maps to | Description | | -------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------- | | `SateaisError` | — | Base class for every SDK error. | | `SateaisApiError` | 4xx / 5xx | Base for API error responses. Has `code` and `status`. | | `AuthenticationError` | `401` / `403` | Missing, invalid, or unauthorized API key. | | `ValidationError` | `400` | Invalid request — bad parameter or polygon over the area cap. | | `InsufficientCreditsError` | `402` | Not enough credits for the job. | | `NotFoundError` | `404` / `410` | Job not found, or result past retention. | | `RateLimitError` | `429` | Request or concurrency limit exceeded. | | `ResponseParseError` | — | A successful (2xx) response body could not be parsed as JSON. Has `status`. Extends `SateaisError`, not `SateaisApiError`. | | `JobFailedError` | — | A job did not complete successfully. Has `jobId`, `errorCode`, `errorMessage`. | | `JobTimeoutError` | — | `jobs.wait()` exceeded `timeoutMs`. Has `jobId`. | ```typescript import { SateaisApiError } from "@sateais/sdk"; try { await client.analyze.ship({ scene_id: "bad-id" }); } catch (err) { if (err instanceof SateaisApiError) { console.error(err.status, err.code, err.message); } } ``` ## Next steps [#next-steps] - [JavaScript SDK guide](https://docs.spcsft.com/sdk/javascript.md): Installation, auth, and configuration. - [API Reference](https://docs.spcsft.com/api-reference.md): The REST contract behind each method. - [Errors](https://docs.spcsft.com/errors.md): Status codes mapped to error classes. - [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): The REST contract behind client.preview. --- # Python SDK > Install, configure, and authenticate the SateAIs Python client. Source: https://docs.spcsft.com/sdk/python The SateAIs Python SDK (`sateais`) is a typed client for the SateAIs API. It resolves credentials automatically, exposes one method per detection type, and includes a `sateais` command-line tool. | | | | ------------ | ---------------- | | Package | `sateais` (PyPI) | | Python | ≥ 3.10 | | Dependencies | `httpx` | ## Installation [#installation] ```bash pip install sateais ``` ## Authentication [#authentication] Create an API key in the [SateAIs API Console](https://console.spcsft.com) (see [Authentication](https://docs.spcsft.com/authentication.md)). The client resolves it from the first available source, in order: 1. The `api_key` argument to `Client(...)`. 2. The `SATEAIS_API_KEY` environment variable. 3. The credentials file at `~/.sateais/credentials` (written by `sateais login`). ```python from sateais import Client # 1. Explicit argument client = Client(api_key="sk_live_xxxxx") # 2. From SATEAIS_API_KEY (recommended) client = Client() ``` If no key can be resolved, the constructor raises `CredentialsNotFoundError`. > **Warning: Keep your key secret** > > Never hard-code `sk_live_` keys in source control. Prefer the `SATEAIS_API_KEY` environment variable or `sateais login`. ## Creating a client [#creating-a-client] ```python from sateais import Client client = Client( api_key=None, # falls back to env / credentials file base_url=None, # defaults to https://api.spcsft.com/api/v1 timeout=30.0, # per-request timeout in seconds ) ``` The base URL falls back to the `SATEAIS_BASE_URL` environment variable, then to the production endpoint. ### Releasing resources [#releasing-resources] `Client` holds an HTTP connection pool. Use it as a context manager, or call `close()` when you are done: ```python with Client() as client: job = client.analyze.ship(scene_id="S1A_IW_GRDH_...") result = client.jobs.wait(job.job_id) # or manually client = Client() try: ... finally: client.close() ``` ## Your first detection [#your-first-detection] ```python from sateais import Client client = Client() # Submit a job job = client.analyze.ship(scene_id="S1A_IW_GRDH_...") print(job.job_id, job.status) # -> "...", JobStatus.PENDING # Poll until completion and fetch the GeoJSON result = client.jobs.wait(job.job_id, poll_interval=30, on_poll=lambda j: print(j.status)) print(len(result["features"]), "ships detected") ``` ## Preview before submitting [#preview-before-submitting] `client.preview.*` mirrors `client.analyze.*` — the same five methods with the same arguments — but creates no job and spends no credits. It returns the area the request is expected to analyse and the credits it would cost, so you can widen the period, split the AOI, or top up before committing: ```python preview = client.preview.newbuilding( polygon="POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start="2026-01-01", date_end="2026-05-01", ) preview.area_sqkm # area expected to be analysed (km²) preview.credits.estimated # expected cost — None means "not determined before submission" preview.credits.balance # current balance preview.credits.sufficient # whether the balance covers it — None when estimated is None preview.credits.shortfall # how much you are short — 0.0 when covered, None when unknown if preview.coverage is not None: # None means "cannot be predicted", not 100% preview.coverage.ratio # fraction of the AOI that will be analysed (0.0–1.0) preview.coverage.polygon # WKT of the area that will be analysed for warning in preview.warnings: print(warning.code, warning.message) ``` A short balance does not raise — that is what `credits.sufficient` is for. `InsufficientCreditsError` is raised on submission only. Every other check runs the same validation as submission, in the same order, so a preview that succeeds means the submission passes validation too. > **Note** > > `credits.estimated` of `None` means the cost is not knowable before submission (a `scene_id` input, for example) — it does not mean the job is free. Likewise `coverage` of `None` means "cannot be predicted", not "100% covered". See [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md). ## Handling errors [#handling-errors] All SDK exceptions inherit from `SateAIsError`. API responses map to typed subclasses that mirror the [API error codes](https://docs.spcsft.com/errors.md): ```python from sateais import Client, RateLimitError, JobFailedError, InsufficientCreditsError client = Client() try: job = client.analyze.timeseries( polygon="POLYGON((...))", date_start="2026-01-01", date_end="2026-05-01", ) result = client.jobs.wait(job.job_id, timeout=3600) except InsufficientCreditsError: print("Top up credits in the Console") except RateLimitError: print("Rate limited — back off and retry") except JobFailedError as err: print("Job failed:", err.job.error_code, err.job.error_message) ``` See the [Reference](https://docs.spcsft.com/sdk/python/reference.md#exceptions) for the full exception hierarchy. ## Command-line interface [#command-line-interface] Installing the package also provides a `sateais` CLI for quick, scriptable runs: ```bash # Store your API key in ~/.sateais/credentials (prompts if --api-key is omitted) sateais login --api-key sk_live_xxxxx # Submit a job, wait for it, and save the result sateais analyze ship --scene-id S1A_IW_GRDH_... --wait -o ships.geojson # Polygon-period analyses sateais analyze timeseries \ --polygon "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))" \ --date-start 2026-01-01 --date-end 2026-05-01 --wait -o result.geojson # Check the analysed area and credit cost without submitting anything sateais preview newbuilding \ --polygon "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))" \ --date-start 2026-01-01 --date-end 2026-05-01 # Inspect or fetch jobs sateais jobs status sateais jobs result -o result.geojson sateais jobs wait --poll-interval 30 -o result.geojson # Decode a Sentinel-1 scene ID locally (no API call) sateais scene S1A_IW_GRDH_... ``` Every `analyze` subcommand accepts `--satellite-id`, `--wait`, `--poll-interval`, `--timeout`, and `-o/--output`. `ship` and `oilslick` additionally take `--scene-id`, `--polygon`, `--date`, `--date-direction`, and `--orbit-direction`; the polygon-period types (`newbuilding`, `disappearbuilding`, `timeseries`) take `--polygon`, `--date-start`, `--date-end`, and `--orbit-direction`. `sateais preview ` takes the same analysis parameters as `sateais analyze ` (minus the waiting flags) and prints the preview JSON. It exits `0` even when the balance is short, because the API answers `200` with `"sufficient": false` rather than an error. > **Note** > > Analysis parameters can also be passed as a single object with `--json` — a JSON string, `@file.json`, or `-` for stdin. Individual flags override the values in the JSON. ## Next steps [#next-steps] - [Python reference](https://docs.spcsft.com/sdk/python/reference.md): Every method, parameter, return type, and exception. - [JavaScript SDK](https://docs.spcsft.com/sdk/javascript.md): The same client for Node and the browser. - [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): What the preview response tells you, field by field. - [Errors](https://docs.spcsft.com/errors.md): Error codes the SDK maps to exceptions. --- # Python reference > Full reference for the SateAIs Python SDK — client, analysis methods, jobs, types, and exceptions. Source: https://docs.spcsft.com/sdk/python/reference Complete reference for the `sateais` package. For setup and authentication, see the [Python SDK guide](https://docs.spcsft.com/sdk/python.md). ## Client [#client] ```python from sateais import Client client = Client(api_key=None, *, base_url=None, timeout=30.0, api=None) ``` | Parameter | Type | Default | Description | | ---------- | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `api_key` | `str \| None` | `None` | API key. Falls back to `SATEAIS_API_KEY`, then `~/.sateais/credentials`. | | `base_url` | `str \| None` | `None` | API base URL. Falls back to `SATEAIS_BASE_URL`, then `https://api.spcsft.com/api/v1`. | | `timeout` | `float` | `30.0` | Per-request HTTP timeout, in seconds. | | `api` | `ApiClient \| None` | `None` | Inject a custom transport implementing the `ApiClient` protocol (`submit_analysis`, `preview_analysis`, `get_job`, `get_job_result`, `close`). Intended for testing — when set, `api_key`, `base_url`, and `timeout` are ignored and no credential lookup happens. | **Attributes** | Attribute | Type | Description | | --------- | --------- | -------------------------------------------------------------- | | `analyze` | `Analyze` | Detection job submission methods. | | `preview` | `Preview` | The same methods as `analyze`, previewed instead of submitted. | | `jobs` | `Jobs` | Job status and result methods. | **Methods** | Method | Description | | ------------------------ | ----------------------------------------------------- | | `close()` | Release the underlying HTTP connection pool. | | `__enter__` / `__exit__` | Context-manager support (`with Client() as client:`). | Raises `CredentialsNotFoundError` if no API key can be resolved. ## Analysis methods [#analysis-methods] All `client.analyze.*` methods submit a detection job and return a [`Job`](#job) immediately with `status` `PENDING`. Use [`jobs.wait()`](#jobswait) to block until the result is ready. ### analyze.ship / analyze.oilslick [#analyzeship--analyzeoilslick] ```python client.analyze.ship( *, scene_id: str | None = None, polygon: str | None = None, date: str | None = None, date_direction: str | None = None, orbit_direction: str | None = None, satellite_id: str = "sentinel-1", ) -> Job ``` `analyze.oilslick(...)` has the same signature. Provide **either** `scene_id` **or** `polygon` + `date`. | Parameter | Type | Required | Description | | ----------------- | -------------------- | ----------- | ---------------------------------------------------------------- | | `scene_id` | `str` | Conditional | Sentinel-1 GRD scene ID. Required when `polygon` is not given. | | `polygon` | `str` (WKT) | Conditional | AOI polygon in EPSG:4326. Required when `scene_id` is not given. | | `date` | `str` (`YYYY-MM-DD`) | Conditional | Reference date for scene selection. Required with `polygon`. | | `date_direction` | `str` | No | `"before"`, `"after"`, or `"nearest"` (default `"nearest"`). | | `orbit_direction` | `str` | No | `"ascending"` or `"descending"`. | | `satellite_id` | `str` | No | Defaults to `"sentinel-1"`. | ```python # By scene ID job = client.analyze.ship(scene_id="S1A_IW_GRDH_...") # By polygon + date (auto scene selection) job = client.analyze.oilslick( polygon="POLYGON((55.5 26.7, 56.1 26.7, 56.1 27.0, 55.5 27.0, 55.5 26.7))", date="2026-03-10", date_direction="nearest", ) ``` ### analyze.newbuilding / analyze.disappearbuilding / analyze.timeseries [#analyzenewbuilding--analyzedisappearbuilding--analyzetimeseries] ```python client.analyze.newbuilding( *, polygon: str, date_start: str, date_end: str, orbit_direction: str | None = None, satellite_id: str = "sentinel-1", ) -> Job ``` `analyze.disappearbuilding(...)` and `analyze.timeseries(...)` share this signature. | Parameter | Type | Required | Description | | ----------------- | -------------------- | -------- | -------------------------------- | | `polygon` | `str` (WKT) | Yes | AOI polygon in EPSG:4326. | | `date_start` | `str` (`YYYY-MM-DD`) | Yes | Start of the comparison period. | | `date_end` | `str` (`YYYY-MM-DD`) | Yes | End of the comparison period. | | `orbit_direction` | `str` | No | `"ascending"` or `"descending"`. | | `satellite_id` | `str` | No | Defaults to `"sentinel-1"`. | ```python job = client.analyze.timeseries( polygon="POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start="2026-01-01", date_end="2026-05-01", ) ``` > **Note** > > Per-endpoint area caps apply: `newbuilding` / `disappearbuilding` ≤ 30,000 km², `timeseries` ≤ 50 km². Exceeding a cap raises `ValidationError` before any credits are consumed. See [Polygons & AOI](https://docs.spcsft.com/concepts/polygons-and-aoi.md). ## Preview methods [#preview-methods] `client.preview.*` has the same five methods as `client.analyze.*`, with identical signatures. Each returns an [`AnalysisPreview`](#analysispreview) instead of a [`Job`](#job): no job is created and no credits are consumed. ```python client.preview.ship(...) # same signature as analyze.ship client.preview.oilslick(...) # same signature as analyze.oilslick client.preview.newbuilding(...) # same signature as analyze.newbuilding client.preview.disappearbuilding(...) # same signature as analyze.disappearbuilding client.preview.timeseries(...) # same signature as analyze.timeseries ``` ```python preview = client.preview.timeseries( polygon="POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start="2026-01-01", date_end="2026-05-01", ) print(preview.credits.estimated, preview.credits.sufficient) ``` Parameter validation is identical to submission: `InvalidAnalysisRequestError` for an invalid parameter combination, `ValidationError` for a rejected request. The one deliberate difference is the balance — a short balance comes back as `credits.sufficient` `False`, never as `InsufficientCreditsError`, since seeing the shortfall is the point of previewing. See [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md) for the underlying contract. ## Job methods [#job-methods] ### jobs.status [#jobsstatus] ```python client.jobs.status(job_id: str) -> Job ``` Fetches the current state of a job (a single poll). | Parameter | Type | Required | Description | | --------- | ----- | -------- | ------------- | | `job_id` | `str` | Yes | The job UUID. | ```python job = client.jobs.status("351e635d-7c25-4ae8-a2a5-60c01a6f434c") print(job.status, job.is_terminal) ``` ### jobs.result [#jobsresult] ```python client.jobs.result(job_id: str) -> dict[str, Any] ``` Returns the GeoJSON `FeatureCollection` for a **completed** job. Raises `NotFoundError` if the job is not complete or the result is past its 30-day retention window. ```python geojson = client.jobs.result("351e635d-7c25-4ae8-a2a5-60c01a6f434c") print(geojson["type"], len(geojson["features"])) ``` ### jobs.wait [#jobswait] ```python client.jobs.wait( job_id: str, *, poll_interval: float = 10.0, timeout: float | None = None, on_poll: Callable[[Job], None] | None = None, max_unknown_polls: int = 10, ) -> dict[str, Any] ``` Polls until the job reaches a terminal state, then returns the result GeoJSON. | Parameter | Type | Default | Description | | ------------------- | ------------------------------- | ------- | -------------------------------------------------------------------- | | `job_id` | `str` | — | The job UUID. | | `poll_interval` | `float` | `10.0` | Seconds between status polls. | | `timeout` | `float \| None` | `None` | Maximum seconds to wait. `None` means no limit. | | `on_poll` | `Callable[[Job], None] \| None` | `None` | Called with the latest `Job` after each poll. | | `max_unknown_polls` | `int` | `10` | Consecutive polls returning an unrecognized status before giving up. | Raises `JobFailedError` if the job fails, `JobTimeoutError` if `timeout` elapses first, or `UnknownJobStatusError` if the status stays unrecognized for `max_unknown_polls` consecutive polls. ```python result = client.jobs.wait( job.job_id, poll_interval=30, timeout=3600, on_poll=lambda j: print(j.status), ) ``` > **Tip** > > A `poll_interval` of 30–60 seconds is usually enough. Runtime varies by endpoint — see [How it works](https://docs.spcsft.com/concepts/how-it-works.md#recommended-polling-interval). ## Types [#types] ### Job [#job] `Job` is a dataclass returned by all analysis and job methods. | Field | Type | Description | | --------------- | ------------- | ------------------------------------------------ | | `job_id` | `str` | Job UUID. | | `status` | `JobStatus` | Current status. | | `created_at` | `str \| None` | Creation timestamp (ISO 8601). | | `completed_at` | `str \| None` | Completion timestamp (ISO 8601). | | `result_path` | `str \| None` | API path to the result, once available. | | `error_code` | `str \| None` | Error code when the job failed. | | `error_message` | `str \| None` | Human-readable error detail when the job failed. | | Property | Type | Description | | -------------- | ------ | -------------------------------------------- | | `is_terminal` | `bool` | `True` if status is `COMPLETED` or `FAILED`. | | `is_completed` | `bool` | `True` if status is `COMPLETED`. | | `is_failed` | `bool` | `True` if status is `FAILED`. | ### JobStatus [#jobstatus] A string enum: `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `UNKNOWN`. ```python from sateais import JobStatus if job.status is JobStatus.COMPLETED: ... ``` ### AnalysisPreview [#analysispreview] Returned by every `client.preview.*` method. A frozen dataclass. | Field | Type | Description | | ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `endpoint_id` | `str` | The detection type the preview was run for. | | `credits` | `PreviewCredits` | Expected cost and current balance. | | `area_sqkm` | `float \| None` | Area expected to be analysed (km²). | | `coverage` | `Coverage \| None` | Expected analysed portion of the AOI. `None` means it cannot be predicted — **not** that the AOI is fully covered. | | `warnings` | `tuple[SceneWarning, ...]` | Warnings that can be determined before submission: `LOW_AOI_COVERAGE` and `CREDITS_NOT_ESTIMABLE`. See [Warnings](https://docs.spcsft.com/errors.md#warnings). | ### PreviewCredits [#previewcredits] | Field | Type | Description | | ------------ | --------------- | ----------------------------------------------------------------------------------------------------------- | | `estimated` | `float \| None` | Expected consumption. `None` means it cannot be determined before submission, **not** that the job is free. | | `balance` | `float \| None` | Current credit balance. | | `sufficient` | `bool \| None` | Whether `balance` covers `estimated`. `None` when `estimated` is `None`. | | Property | Type | Description | | ----------- | --------------- | ---------------------------------------------------------------------------------------------------------------- | | `shortfall` | `float \| None` | How many credits you are short. `0.0` when the balance covers the estimate, `None` when either value is unknown. | The estimate is an upper bound: it is based on the area you requested, while the actual charge excludes portions without usable data. Actual consumption never exceeds `estimated`. ### Coverage [#coverage] | Field | Type | Description | | --------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `method` | `CoverageMethod` | How the figure was derived. Always `ESTIMATED` in a preview. | | `requested_area_sqkm` | `float \| None` | Area of the polygon you submitted (km²). | | `ratio` | `float \| None` | Fraction of the AOI expected to be analysed (0.0–1.0). | | `polygon` | `str \| None` | WKT of the area that will be analysed, in the same format as the request `polygon` — so it can be fed straight back into a follow-up request. `None` when the area cannot be expressed as WKT. | ### CoverageMethod [#coveragemethod] A string enum: `MEASURED`, `ESTIMATED`, `UNKNOWN`. A preview is always `ESTIMATED`; `MEASURED` appears on a finished job, where the analysed area is known exactly. `UNKNOWN` is the fallback for a value the SDK does not recognise. ### SceneWarning [#scenewarning] | Field | Type | Description | | --------- | ----- | ------------------------------ | | `code` | `str` | Machine-readable warning code. | | `message` | `str` | Human-readable explanation. | ### Result GeoJSON [#result-geojson] `jobs.result()` and `jobs.wait()` return a plain `dict` — a standard GeoJSON `FeatureCollection` in EPSG:4326: ```json { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "...": "..." }, "properties": { "...": "..." } } ] } ``` ## Exceptions [#exceptions] All exceptions inherit from `SateAIsError`. | Exception | Maps to | Description | | ----------------------------- | ------------- | ----------------------------------------------------------------------------------- | | `SateAIsError` | — | Base class for every SDK error. | | `APIError` | 4xx / 5xx | Base for API error responses. Has `status_code`, `code`, `message`. | | `AuthenticationError` | `401` / `403` | Missing, invalid, or unauthorized API key. | | `ValidationError` | `400` | Invalid request — bad parameter or polygon over the area cap. | | `InsufficientCreditsError` | `402` | Not enough credits for the job. | | `NotFoundError` | `404` / `410` | Job not found, or result past retention. | | `RateLimitError` | `429` | Request or concurrency limit exceeded. | | `JobFailedError` | — | A job ended in `FAILED`. The failed `Job` is on `.job`. | | `JobTimeoutError` | — | `jobs.wait()` exceeded its `timeout`. | | `UnknownJobStatusError` | — | `jobs.wait()` saw an unrecognized status for `max_unknown_polls` consecutive polls. | | `CredentialsNotFoundError` | — | No API key could be resolved. | | `InvalidAnalysisRequestError` | — | Invalid combination of analysis parameters (raised client-side before the request). | ```python from sateais import APIError try: client.analyze.ship(scene_id="bad-id") except APIError as err: print(err.status_code, err.code, err.message) ``` ## Credential helpers [#credential-helpers] ```python from sateais import load_api_key, save_api_key save_api_key("sk_live_xxxxx") # writes ~/.sateais/credentials (0600) key = load_api_key() # returns the stored key or None ``` These back the `sateais login` command and the credentials-file fallback in `Client`. ## Next steps [#next-steps] - [Python SDK guide](https://docs.spcsft.com/sdk/python.md): Installation, auth, and the CLI. - [API Reference](https://docs.spcsft.com/api-reference.md): The REST contract behind each method. - [Errors](https://docs.spcsft.com/errors.md): Status codes mapped to exceptions. - [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): The REST contract behind client.preview. --- # SDK Quickstart > Submit a detection job, wait for it, and save the GeoJSON result with the Python or JavaScript SDK. Source: https://docs.spcsft.com/sdk/quickstart This guide runs a full detection end-to-end — submit, poll, and save the result — with the official SDKs. It takes about five minutes. ## Prerequisites [#prerequisites] * A [SateAIs API Console account](https://console.spcsft.com) * An API key (`sk_live_...`) — see [Authentication](https://docs.spcsft.com/authentication.md) * Python ≥ 3.10, or Node.js ≥ 18 ## 1. Install [#1-install] ```bash pip install sateais ``` ```bash npm install @sateais/sdk ``` ## 2. Set your API key [#2-set-your-api-key] Both SDKs read the `SATEAIS_API_KEY` environment variable automatically, so you don't have to pass the key in code: ```bash export SATEAIS_API_KEY=sk_live_xxxxx ``` > **Tip** > > You can also pass the key explicitly: `Client(api_key="sk_live_xxxxx")` in Python or `new Client({ apiKey: "sk_live_xxxxx" })` in JavaScript. Keep keys out of source control. ## 3. Submit, wait, and save [#3-submit-wait-and-save] Submit an oil slick detection by scene ID, poll until it completes, and write the GeoJSON to a file. Scene IDs come from [ASF Search](https://search.asf.alaska.edu/#/) — filter **File Type = GRD**. ```python import json from sateais import Client client = Client() # Submit the job job = client.analyze.oilslick(scene_id="S1A_IW_GRDH_...") print("submitted:", job.job_id) # Poll until completion (prints status on each poll), then fetch the GeoJSON result = client.jobs.wait(job.job_id, poll_interval=30, on_poll=lambda j: print(j.status)) # Save the result with open("result.geojson", "w") as f: json.dump(result, f) print(len(result["features"]), "detections saved to result.geojson") ``` ```typescript import { writeFile } from "node:fs/promises"; import { Client } from "@sateais/sdk"; const client = new Client(); // Submit the job const job = await client.analyze.oilslick({ scene_id: "S1A_IW_GRDH_..." }); console.log("submitted:", job.job_id); // Poll until completion (logs status on each poll), then fetch the GeoJSON const result = await client.jobs.wait(job.job_id, { intervalMs: 30_000, onPoll: (j) => console.log(j.status), }); // Save the result await writeFile("result.geojson", JSON.stringify(result)); console.log(result.features.length, "detections saved to result.geojson"); ``` The result is a standard GeoJSON `FeatureCollection` in EPSG:4326. View it on the map in the [SateAIs API Console](https://console.spcsft.com), or open the file in any GIS tool. > **Note** > > `jobs.wait()` blocks until the job reaches a terminal state — see [How it works](https://docs.spcsft.com/concepts/how-it-works.md#recommended-polling-interval) for runtime estimates and polling guidance. ## Preview before you submit [#preview-before-you-submit] `preview.*` takes exactly the same parameters as `analyze.*` and answers two questions before you commit: which part of your AOI will actually be analysed, and how many credits that will cost. No job is created and no credits are spent. ```python preview = client.preview.newbuilding( polygon="POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start="2026-01-01", date_end="2026-05-01", ) print(preview.credits.estimated, "credits estimated") print(preview.credits.balance, "credits available") if preview.coverage is not None: print(preview.coverage.ratio, "of the AOI will be analysed") if preview.credits.sufficient: job = client.analyze.newbuilding(...) # same arguments, now submitted ``` ```typescript const preview = await client.preview.newbuilding({ polygon: "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start: "2026-01-01", date_end: "2026-05-01", }); console.log(preview.credits.estimated, "credits estimated"); console.log(preview.credits.balance, "credits available"); console.log(preview.coverage?.ratio, "of the AOI will be analysed"); if (preview.credits.sufficient) { const job = await client.analyze.newbuilding({ /* same params */ }); } ``` A short balance is not an error here: the preview returns `sufficient: false` so you can see how much you are missing. See [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md) for the full response contract. ## Submitting other detections [#submitting-other-detections] `analyze.ship` and `analyze.oilslick` accept either a `scene_id` or a `polygon` + `date`. The polygon-period detections (`newbuilding`, `disappearbuilding`, `timeseries`) take a `polygon` and a `date_start` / `date_end`: ```python job = client.analyze.timeseries( polygon="POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start="2026-01-01", date_end="2026-05-01", ) ``` ```typescript const job = await client.analyze.timeseries({ polygon: "POLYGON((139.7 35.6, 139.8 35.6, 139.8 35.7, 139.7 35.7, 139.7 35.6))", date_start: "2026-01-01", date_end: "2026-05-01", }); ``` See the full method list in the [Python](https://docs.spcsft.com/sdk/python/reference.md) and [JavaScript](https://docs.spcsft.com/sdk/javascript/reference.md) references. ## Next steps [#next-steps] - [Python reference](https://docs.spcsft.com/sdk/python/reference.md): Every method, parameter, return type, and exception. - [JavaScript reference](https://docs.spcsft.com/sdk/javascript/reference.md): Every method, parameter, return type, and error class. - [Preview a request](https://docs.spcsft.com/api-reference/analyze/preview.md): What the preview response tells you, field by field. - [Errors](https://docs.spcsft.com/errors.md): How status codes map to SDK exceptions.