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 (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

AttributeTypeDescription
analyzeAnalyzeDetection job submission methods.
previewPreviewThe same methods as analyze, previewed instead of submitted.
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.

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.timeseries
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 for the underlying contract.

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_idstr—The 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

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.

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:
    ...

AnalysisPreview

Returned by every client.preview.* method. A frozen dataclass.

FieldTypeDescription
endpoint_idstrThe detection type the preview was run for.
creditsPreviewCreditsExpected cost and current balance.
area_sqkmfloat | NoneArea expected to be analysed (km²).
coverageCoverage | NoneExpected analysed portion of the AOI. None means it cannot be predicted — not that the AOI is fully covered.
warningstuple[SceneWarning, ...]Warnings that can be determined before submission: LOW_AOI_COVERAGE and CREDITS_NOT_ESTIMABLE. See Warnings.

PreviewCredits

FieldTypeDescription
estimatedfloat | NoneExpected consumption. None means it cannot be determined before submission, not that the job is free.
balancefloat | NoneCurrent credit balance.
sufficientbool | NoneWhether balance covers estimated. None when estimated is None.
PropertyTypeDescription
shortfallfloat | NoneHow 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

FieldTypeDescription
methodCoverageMethodHow the figure was derived. Always ESTIMATED in a preview.
requested_area_sqkmfloat | NoneArea of the polygon you submitted (km²).
ratiofloat | NoneFraction of the AOI expected to be analysed (0.0–1.0).
polygonstr | NoneWKT 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

FieldTypeDescription
codestrMachine-readable warning code.
messagestrHuman-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.

ExceptionMaps toDescription
SateAIsError—Base 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.
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 None

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

Next steps

On this page