Python

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)
ParameterTypeDefaultDescription
api_keystr | NoneNoneAPI key. Falls back to SATEAIS_API_KEY, then ~/.sateais/credentials.
base_urlstr | NoneNoneAPI base URL. Falls back to SATEAIS_BASE_URL, then https://api.spcsft.com/api/v1.
timeoutfloat30.0Per-request HTTP timeout, in seconds.
apiApiClient | NoneNoneInject 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

AttributeTypeDescription
analyzeAnalyzeDetection job submission methods.
jobsJobsJob status and result methods.

Methods

MethodDescription
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",
) -> Job

analyze.oilslick(...) has the same signature. Provide either scene_id or polygon + date.

ParameterTypeRequiredDescription
scene_idstrConditionalSentinel-1 GRD scene ID. Required when polygon is not given.
polygonstr (WKT)ConditionalAOI polygon in EPSG:4326. Required when scene_id is not given.
datestr (YYYY-MM-DD)ConditionalReference date for scene selection. Required with polygon.
date_directionstrNo"before", "after", or "nearest" (default "nearest").
orbit_directionstrNo"ascending" or "descending".
satellite_idstrNoDefaults 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",
) -> Job

analyze.disappearbuilding(...) and analyze.timeseries(...) share this signature.

ParameterTypeRequiredDescription
polygonstr (WKT)YesAOI polygon in EPSG:4326.
date_startstr (YYYY-MM-DD)YesStart of the comparison period.
date_endstr (YYYY-MM-DD)YesEnd of the comparison period.
orbit_directionstrNo"ascending" or "descending".
satellite_idstrNoDefaults 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) -> Job

Fetches the current state of a job (a single poll).

ParameterTypeRequiredDescription
job_idstrYesThe 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.

ParameterTypeDefaultDescription
job_idstrThe job UUID.
poll_intervalfloat10.0Seconds between status polls.
timeoutfloat | NoneNoneMaximum seconds to wait. None means no limit.
on_pollCallable[[Job], None] | NoneNoneCalled with the latest Job after each poll.
max_unknown_pollsint10Consecutive 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.

FieldTypeDescription
job_idstrJob UUID.
statusJobStatusCurrent status.
created_atstr | NoneCreation timestamp (ISO 8601).
completed_atstr | NoneCompletion timestamp (ISO 8601).
result_pathstr | NoneAPI path to the result, once available.
error_codestr | NoneError code when the job failed.
error_messagestr | NoneHuman-readable error detail when the job failed.
PropertyTypeDescription
is_terminalboolTrue if status is COMPLETED or FAILED.
is_completedboolTrue if status is COMPLETED.
is_failedboolTrue 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.

ExceptionMaps toDescription
SateAIsErrorBase class for every SDK error.
APIError4xx / 5xxBase for API error responses. Has status_code, code, message.
AuthenticationError401 / 403Missing, invalid, or unauthorized API key.
ValidationError400Invalid request — bad parameter or polygon over the area cap.
InsufficientCreditsError402Not enough credits for the job.
NotFoundError404 / 410Job not found, or result past retention.
RateLimitError429Request or concurrency limit exceeded.
JobFailedErrorA job ended in FAILED. The failed Job is on .job.
JobTimeoutErrorjobs.wait() exceeded its timeout.
UnknownJobStatusErrorjobs.wait() saw an unrecognized status for max_unknown_polls consecutive polls.
CredentialsNotFoundErrorNo API key could be resolved.
InvalidAnalysisRequestErrorInvalid 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 None

These back the sateais login command and the credentials-file fallback in Client.

Next steps

On this page