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 (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
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.
Preview methods
client.preview.* has the same five methods as client.analyze.*, with identical signatures. Each returns an AnalysisPreview instead of a Job: no job is created and no credits are consumed.
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.timeseriespreview = 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 for the underlying contract.
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
A poll_interval of 30–60 seconds is usually enough. Runtime varies by endpoint — 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:
...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. |
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
| 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
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
| Field | Type | Description |
|---|---|---|
code | str | Machine-readable warning code. |
message | str | Human-readable explanation. |
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.