Python reference
Full reference for the SateAIs Python SDK — client, analysis methods, jobs, types, and exceptions.
Complete reference for the sateais package. For setup and authentication, see the Python SDK guide.
Client
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. 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. |
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
All client.analyze.* methods submit a detection job and return a Job immediately with status PENDING. Use jobs.wait() to block until the result is ready.
analyze.ship / analyze.oilslick
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",
) -> Jobanalyze.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". |
# 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
client.analyze.newbuilding(
*,
polygon: str,
date_start: str,
date_end: str,
orbit_direction: str | None = None,
satellite_id: str = "sentinel-1",
) -> Jobanalyze.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". |
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",
)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.
Job methods
jobs.status
client.jobs.status(job_id: str) -> JobFetches the current state of a job (a single poll).
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | str | Yes | The job UUID. |
job = client.jobs.status("351e635d-7c25-4ae8-a2a5-60c01a6f434c")
print(job.status, job.is_terminal)jobs.result
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.
geojson = client.jobs.result("351e635d-7c25-4ae8-a2a5-60c01a6f434c")
print(geojson["type"], len(geojson["features"]))jobs.wait
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.
result = client.jobs.wait(
job.job_id,
poll_interval=30,
timeout=3600,
on_poll=lambda j: print(j.status),
)Tip
Detection jobs typically run 30–60 minutes. A poll_interval of 30–60 seconds is usually enough — see How it works.
Types
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
A string enum: PENDING, PROCESSING, COMPLETED, FAILED, UNKNOWN.
from sateais import JobStatus
if job.status is JobStatus.COMPLETED:
...Result GeoJSON
jobs.result() and jobs.wait() return a plain dict — a standard GeoJSON FeatureCollection in EPSG:4326:
{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "geometry": { "...": "..." }, "properties": { "...": "..." } }
]
}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). |
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
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 NoneThese back the sateais login command and the credentials-file fallback in Client.