Sign in

SDK

The Cecil SDK is an open source Python library that allows you to use the Cecil platform. The SDK integrates with Cecil's internal API using standard HTTP methods, authentication, and response codes. Data resources use RFC 3339 timestamps and universally unique identifiers (UUID v4).

Installation

Install the SDK in your project virtual environment. Python ≥ 3.10 is required.

pip install cecil

Authentication

Configure the SDK by setting the CECIL_API_KEY environment variable. Only store your API key in an encrypted vault or secrets manager, never in code or plain/text files. Don't have an account? Sign up.

# Linux and macOS
export CECIL_API_KEY="my-api-key"

# Windows PowerShell
$env:CECIL_API_KEY = "my-api-key"

Usage

All SDK functions require authentication unless otherwise specified.

import cecil

client = cecil.Client()

client.function_name()

AOI

The area of interest (AOI) represents a geographic area.

Property

Type

Description

id

uuid

Unique identifier.

geometry required

object

GeoJSON geometry object in EPSG:4326 delimiting the boundary.

external_ref

string

External reference in your system, e.g. AOI ID or name.

hectares

float

Total size derived from the geometry. This is useful for cost tracking.

created_at

datetime

Current system time when the AOI was created.

created_by

uuid

Authenticated user who created the AOI.

archived_at

datetime

Current system time when the AOI was archived, or null.

archived_by

uuid

Authenticated user who archived the AOI, or null.

The AOI geometry is a GeoJSON geometry object in EPSG:4326 delimiting the AOI boundaries.

Property

Type

Description

type required

string

Point, MultiPoint, Polygon, or MultiPolygon.

coordinates required

array

Nested array of coordinates according to the geometry type.

The geometry must be a valid geometry according to the Open Geospatial Consortium specification. There is no limit on AOI geometry complexity: boundaries can have any number of vertices and interior rings, and excluded interior areas are not counted towards hectares. Individual datasets may apply their own AOI constraints when a subscription is created (e.g. max vertices, min/max area).

Create AOI

This function allows you to create an AOI.

Creating an AOI with a geometry identical to one of your existing AOIs returns the existing AOI rather than a duplicate, so re-running a script converges on the same IDs. Passing a different external_ref creates a separate AOI.

Input

client.create_aoi(
    external_ref="123",
    geometry={
        "type": "Polygon",
        "coordinates": [[
            [132.52934211276073, -12.721072673008706],
            [132.52934211276073, -12.730063400794094],
            [132.54027735328083, -12.730063400794094],
            [132.54027735328083, -12.721072673008706],
            [132.52934211276073, -12.721072673008706],
        ]],
    },
)

Output

AOI(
    id="fa200894-4e13-456c-872a-ba8efcda0812",
    external_ref="123",
    geometry={
        "type": "Polygon",
        "coordinates": [[
            [132.52934211276073, -12.721072673008706],
            [132.52934211276073, -12.730063400794094],
            [132.54027735328083, -12.730063400794094],
            [132.54027735328083, -12.721072673008706],
            [132.52934211276073, -12.721072673008706],
        ]],
    },
    hectares=118.12168458669186,
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
    archived_at=None,
    archived_by=None,
)

List AOIs

This function allows you to list all AOIs that are currently active (not archived) in your organisation. You can optionally pass in archived=True to list all archived AOIs.

Input

client.list_aois(archived=False)

Output

[
    AOI(
        id="fa200894-4e13-456c-872a-ba8efcda0812",
        external_ref="123",
        hectares=118.12168458669186,
        created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
        created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
        archived_at=None,
        archived_by=None,
    ),
]

Get AOI

This function allows you to get an AOI by ID, regardless of being archived.

Input

client.get_aoi("fa200894-4e13-456c-872a-ba8efcda0812")

Output

AOI(
    id="fa200894-4e13-456c-872a-ba8efcda0812",
    external_ref="123",
    geometry={
        "type": "Polygon",
        "coordinates": [[
            [132.52934211276073, -12.721072673008706],
            [132.52934211276073, -12.730063400794094],
            [132.54027735328083, -12.730063400794094],
            [132.54027735328083, -12.721072673008706],
            [132.52934211276073, -12.721072673008706],
        ]],
    },
    hectares=118.12168458669186,
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
    archived_at=None,
    archived_by=None,
)

Archive AOI

This function allows you to archive an AOI by ID. When you archive an AOI, associated subscriptions remain available, but new subscriptions can no longer be created for the archived AOI.

Input

client.archive_aoi("fa200894-4e13-456c-872a-ba8efcda0812")

Output

No content

Restore AOI

This function allows you to restore an AOI by ID. You can restore an AOI from the archive at any time.

Input

client.restore_aoi("fa200894-4e13-456c-872a-ba8efcda0812")

Output

No content

Dataset

The dataset represents a product offering by a data provider. All dataset information available in the SDK uses the same source of truth as the list of datasets in the documentation. This allows you to programmatically analyse datasets before making a data acquisition. All attributes are provided, unless otherwise specified as optional.

Dataset(
    id="f42f8f48-6cf9-48af-8453-be6cca46acb8",
    name="Dataset name",
    type="Raster|Vector",
    crs="EPSG:4326", # optional; when null, see description

    description=[
        "Multi paragraph description",
    ],

    usage_notes=[
        "Multi paragraph usage notes",
    ],

    categories=[
        "Dataset category",
    ],

    licence=Licence(
        type="Open|Commercial",
    ),

    version=Version(
        number="1.0",      # optional
        date="2026-01-01", # optional
    ),

    spatial_coverage=SpatialCoverage(
        nominal="Global", # optional
    ),

    spatial_resolution=SpatialResolution(
        nominal="30 m",   # optional
        units="degrees",  # optional
        x=0.0025,         # optional
        y=0.0025,         # optional
    ),

    temporal_coverage=TemporalCoverage(
        nominal="2015+", # optional
    ),

    temporal_resolution=TemporalResolution(
        nominal="Annual", # optional
    ),

    constraints=Constraints(
        aoi_min_hectares=1,   # optional
        aoi_max_hectares=10,  # optional
        aoi_min_latitude=1,   # optional
        aoi_max_latitude=10,  # optional
        aoi_max_vertices=100, # optional
        aoi_geometry_types=["Polygon", "MultiPolygon"],
        organisation_verified_only=False,
    ),

    variables=[
        Variable(
            name="variable_name",
            type="float32",
            no_data="255", # optional
            units="m³",    # optional
            description=[
                "Multi paragraph description",
            ],
            usage_notes=[
                "Multi paragraph usage notes", # optional
            ],
            reference_table=[
                {}, # optional
            ],
        ),
    ],

    pricing=Pricing(
        description=[
            "Multi paragraph description",
        ],
        tiers=[
            Tier( # optional; e.g. open licence, or see description
                volume=Volume(
                    nominal="Any",
                ),
                price=Price(
                    amount=0.10,
                    nominal="$0.10/ha",
                ),
            ),
        ],
    ),

    resources=[
        Resource(
            type="Licensing",
            title="Terms of Service",
            href="https://example.com/terms",
        ),
    ],

    providers=[
        Provider( # the main provider is the first one in the list
            name="Provider name",
            website="https://example.com",
            description=[
                "Multi paragraph description",
            ],
        ),
    ],
)

List datasets

This function allows you to list all datasets available in the Cecil platform.

Input

client.list_datasets()

Output

[
    Dataset(
        # Omitting large payload for readability
        # Please refer to the full specification above
    ),
]

Get dataset

This function allows you to get a dataset by ID.

Input

client.get_dataset("f42f8f48-6cf9-48af-8453-be6cca46acb8")

Output

Dataset(
    # Omitting large payload for readability
    # Please refer to the full specification above
),

Subscription

The subscription represents a dataset acquisition for your AOI.

Property

Type

Description

id

uuid

Unique identifier.

aoi_id required

string

Unique identifier of the AOI associated with this subscription.

dataset_id required

string

Unique identifier of the dataset associated with this subscription. You can find this information on the details page of available datasets.

external_ref

string

External reference in your system, e.g. data subscription ID, use case ID, or description of the use case.

dataset_publication

string

Publication of the dataset this subscription was created under. Never changes for the life of the subscription.

dataset_current_publication

string

The dataset's current publication. When it differs from dataset_publication, a newer publication exists and you can create a new subscription to pick it up.

status

string

Delivery status: pending, processing, completed, partial or failed.

status_message

string

Detail when the status needs it, e.g. the provider's error or how long delivery has been quiet. Otherwise null.

created_at

datetime

Current system time when the subscription was created.

created_by

uuid

Authenticated user who created the subscription.

archived_at

datetime

Current system time when the subscription was archived, or null.

archived_by

uuid

Authenticated user who archived the subscription, or null.

Create subscription

This function allows you to acquire datasets for your AOI. This process runs in the background and may take from a few minutes to a few business days depending on the data provider.

Creating a subscription for an AOI and dataset you already have an active subscription to returns an error naming the existing subscription — re-running a script won't silently create (and bill) the same subscription twice. To create a duplicate deliberately, for example to pick up a new dataset version, pass allow_duplicate=True, or archive the old subscription first.

Input

client.create_subscription(
    aoi_id="fa200894-4e13-456c-872a-ba8efcda0812",
    dataset_id="f42f8f48-6cf9-48af-8453-be6cca46acb8",
    external_ref="123",
)

Output

Subscription(
    id="f644efb3-8ba4-4bc4-8333-1ace54a3f111",
    aoi_id="fa200894-4e13-456c-872a-ba8efcda0812",
    dataset_id="f42f8f48-6cf9-48af-8453-be6cca46acb8",
    external_ref="123",
    dataset_publication="1.0",
    dataset_current_publication="1.0",
    status="pending",
    status_message=None,
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
    archived_at=None,
    archived_by=None,
)

List subscriptions

This function allows you to list all subscriptions that are currently active (not archived) in your organisation. You can optionally pass in archived=True to list all archived subscriptions.

Input

client.list_subscriptions(archived=False)

Output

[
    Subscription(
        id="f644efb3-8ba4-4bc4-8333-1ace54a3f111",
        aoi_id="fa200894-4e13-456c-872a-ba8efcda0812",
        dataset_id="f42f8f48-6cf9-48af-8453-be6cca46acb8",
        external_ref="123",
        created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
        created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
        archived_at=None,
        archived_by=None,
    ),
]

Get subscription

This function allows you to get a subscription by ID, regardless of being archived.

Input

client.get_subscription("f644efb3-8ba4-4bc4-8333-1ace54a3f111")

Output

Subscription(
    id="f644efb3-8ba4-4bc4-8333-1ace54a3f111",
    aoi_id="fa200894-4e13-456c-872a-ba8efcda0812",
    dataset_id="f42f8f48-6cf9-48af-8453-be6cca46acb8",
    external_ref="123",
    dataset_publication="1.0",
    dataset_current_publication="1.0",
    status="completed",
    status_message=None,
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
    archived_at=None,
    archived_by=None,
)

Archive subscription

This function allows you to archive a subscription by ID. When you archive a subscription, you have a grace period of 30 days to restore the subscription. Once the grace period is over, the subscription can no longer be restored from the archive, and the subscription data is permanently deleted.

Input

client.archive_subscription("f644efb3-8ba4-4bc4-8333-1ace54a3f111")

Output

No content

Restore subscription

This function allows you to restore a subscription by ID within the archive grace period.

Input

client.restore_subscription("f644efb3-8ba4-4bc4-8333-1ace54a3f111")

Output

No content

Data access

You can access Cecil datasets with the following functions. Additionally, you can develop interactive data visualisations with the Earthscale integration.

Function

Dataset type

Load xarray

raster

Load dataframe

vector

Load xarray

This function loads a raster dataset into an xarray.Dataset for an active subscription. Raster datasets always have x, y dimensions and most times a time dimension. The following metadata is available in dataset-level attributes. See datasets for usage notes. From SDK 0.1.19 this function emits a UserWarning when the subscription's status is not completed, so partially delivered data is never silent.

Dimension

Type

Units

x

Per dataset variable

Per CRS.

y

Per dataset variable

Per CRS.

time

datetime

Per dataset, when a time dimension is available.

Metadata

provider_name

dataset_name

dataset_id

aoi_id

subscription_id

Input

client.load_xarray(
    subscription_id="f644efb3-8ba4-4bc4-8333-1ace54a3f111",
)

Output

<xarray.Dataset> Size: 3MB
Dimensions:        (x: 50, y: 50, time: 20)
Coordinates:
  * x              (x) float64 512B 68.57 68.57 68.57 ... 68.56 68.56
  * y              (y) float64 512B 45.78 45.78 45.78 ... 45.76 45.76 45.76
  * time           (time) datetime64[ns] 2kB 2021-03-21 ... 2025-12-21
Data variables:
    canopy_cover   (x, y, time) float64 3MB dask.array<chunksize=(50, 50, 1), meta=np.ndarray>
    canopy_height  (x, y, time) float64 3MB dask.array<chunksize=(50, 50, 1), meta=np.ndarray>
Attributes:
    provider_name:    Planet
    dataset_name:     Forest Carbon Monitoring
    dataset_id:       f42f8f48-6cf9-48af-8453-be6cca46acb8
    aoi_id:           fa200894-4e13-456c-872a-ba8efcda0812
    subscription_id:  f644efb3-8ba4-4bc4-8333-1ace54a3f111

Some raster variables are stored packed with a scale and offset (for example Landsat surface reflectance, stored as uint16 with scale 0.0000275 and offset -0.2). By default, load_xarray() decodes these variables for you: values are returned as physical float32 with NaN as the no-data value, and the scale_factor and add_offset move to each variable's .encoding. Variables without a declared scale and offset are unaffected. The variable types shown on dataset pages describe this default, decoded output.

To load the raw packed values instead, pass mask_and_scale=False. Values keep their native dtype and no-data value, and the true scale_factor and add_offset are set as attributes on each variable — apply value × scale_factor + add_offset yourself, masking no-data values first.

client.load_xarray(
    subscription_id="f644efb3-8ba4-4bc4-8333-1ace54a3f111",
    mask_and_scale=False,
)

Load dataframe

This function loads a vector dataset into a pandas.DataFrame for an active subscription. Vector datasets have the following metadata in dataframe columns. See datasets for usage notes. From SDK 0.1.19 this function emits a UserWarning when the subscription's status is not completed, so partially delivered data is never silent.

Metadata

aoi_id

subscription_id

Input

client.load_dataframe(
    subscription_id="f644efb3-8ba4-4bc4-8333-1ace54a3f111",
)

Output

     canopy_cover  timestamp
0            0.73  datetime.datetime(2021, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC))
1            0.79  datetime.datetime(2022, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC))
2            0.87  datetime.datetime(2023, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC))

[3 rows x 2 columns]

Webhooks

Webhooks allow you to receive real-time notifications from the Cecil platform. The webhook url must use https, handle POST requests, and return a successful status (200-299) within 10 seconds. Failed requests will be retried 3 times at an interval of approximately 12 hours.

Property

Type

Description

id

uuid

Unique identifier.

url required

string

Your application URL to receive webhook events.

secret

string

Optional secret for signature validation.

events

array

Event types this webhook receives. Defaults to all three when omitted.

created_at

datetime

Current system time when the webhook was created.

created_by

uuid

Authenticated user who created the webhook.

Create webhook

This function allows you to create a webhook.

Input

client.create_webhook(
    url="https://api.example.com/cecil-webhook",
    secret="YT5MzU9xk3dhMDjZ",
    events=["subscription.delivered", "subscription.failed", "subscription.completed"],
)

Output

Webhook(
    id="fc9dbf41-a44c-4f13-8bac-1920dd98c39f",
    url="https://api.example.com/cecil-webhook",
    events=["subscription.delivered", "subscription.failed", "subscription.completed"],
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
)

List webhooks

This function allows you to list all webhooks in your organisation.

Input

client.list_webhooks()

Output

[
    Webhook(
        id="fc9dbf41-a44c-4f13-8bac-1920dd98c39f",
        url="https://api.example.com/cecil-webhook",
        events=["subscription.delivered", "subscription.failed", "subscription.completed"],
        created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
        created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
    ),
]

Get webhook

This function allows you to get a webhook by ID.

Input

client.get_webhook("fc9dbf41-a44c-4f13-8bac-1920dd98c39f")

Output

Webhook(
    id="fc9dbf41-a44c-4f13-8bac-1920dd98c39f",
    url="https://api.example.com/cecil-webhook",
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
)

Delete webhook

This function allows you to delete a webhook by ID, stopping notifications immediately.

Input

client.delete_webhook("fc9dbf41-a44c-4f13-8bac-1920dd98c39f")

Output

No content

Signature validation

We recommend you to use the Cecil-Signature header to validate the authenticity and integrity of webhook events. Below is a code example in Python.

import hashlib
import hmac

body = "request body received in the http request"
signature = "value from the Cecil-Signature header"
secret = "your-webhook-secret"

digest = hmac.new(
    digestmod=hashlib.sha256
    key=secret.encode("utf-8"),
    msg=body.encode("utf-8"),
).hexdigest()

if hmac.compare_digest(digest, signature):
    print("valid")

Events

Webhook notifications include the following events.

Event name

Event type

Subscription delivered

subscription.delivered

Subscription failed

subscription.failed

Subscription completed

subscription.completed

Subscription delivered

This event notifies that one data chunk has been delivered for a subscription. For datasets with a time dimension, a data chunk is one variable by time point combination. For datasets without a time dimension, a data chunk is one variable, and the payload.time property is set to null. For datasets with ongoing time series, future time points arrive as additional events.

Example

{
    "event_id": "f309e366-13dc-5f6c-8cad-c0c3c34bb548",
    "event_type": "subscription.delivered",
    "payload": {
        "aoi_id": "fa200894-4e13-456c-872a-ba8efcda0812",
        "subscription_id": "f644efb3-8ba4-4bc4-8333-1ace54a3f111",
        "dataset_id": "f42f8f48-6cf9-48af-8453-be6cca46acb8",
        "dataset_type": "raster",
        "time": "2026-01-09T00:00:00Z",
        "variable": "x_value"
    },
    "recorded_at": "2026-01-01T15:30:09.033Z"
}

Subscription failed

This event notifies that a subscription failed to process. Possible failures are data not being found for the specified AOI or constraints from data providers. For failures caused by data provider constraints, the payload.error_message contains the original error message from the data provider.

Example

{
    "event_id": "f309e366-13dc-5f6c-8cad-c0c3c34bb548",
    "event_type": "subscription.failed",
    "payload": {
        "aoi_id": "fa200894-4e13-456c-872a-ba8efcda0812",
        "subscription_id": "f644efb3-8ba4-4bc4-8333-1ace54a3f111",
        "dataset_id": "f42f8f48-6cf9-48af-8453-be6cca46acb8",
        "dataset_type": "vector",
        "error_message": "Data not found."
    },
    "recorded_at": "2026-01-01T15:30:09.033Z"
}

Subscription completed

This event notifies that a subscription has completed delivery up to the current time point. For datasets with ongoing time series, future time points arrive as additional subscription.delivered events and the status remains completed.

Example

{
    "event_id": "f309e366-13dc-5f6c-8cad-c0c3c34bb548",
    "event_type": "subscription.completed",
    "payload": {
        "aoi_id": "fa200894-4e13-456c-872a-ba8efcda0812",
        "subscription_id": "f644efb3-8ba4-4bc4-8333-1ace54a3f111",
        "dataset_id": "f42f8f48-6cf9-48af-8453-be6cca46acb8",
        "dataset_type": "raster",
        "message": null
    },
    "recorded_at": "2026-01-01T15:30:09.033Z"
}

Organisation settings

Organisation settings allow you to configure the following organisation-wide settings.

Property

Type

Description

monthly_subscription_limit

integer

Number of hectares across subscriptions that can be created per month. This value can only be configured for verified organisations. Please contact support@cecil.earth to verify your organisation. Default: 50,000.

Get organisation settings

This function allows you to view your organisation settings.

Input

client.get_settings()

Output

Settings(
    monthly_subscription_limit=50_000,
)

Update organisation settings

This function allows you to update your organisation settings.

Input

client.update_settings(
    monthly_subscription_limit=50_000,
)

Output

No content

User management

User management allows creating and viewing users in your organisation.

Property

Type

Description

id

uuid

Unique identifier.

first_name required

string

First name.

last_name required

string

Last name.

email required

string

Email address.

created_at

datetime

Current system time when the user was created.

created_by

uuid

Authenticated user who created the user.

Create user

This function allows you to create an organisation user. The new user will receive an email with instructions to generate their API key. The link sent via email expires in 7 days. If the link expires, the recover function can be used to generate their API key.

Input

client.create_user(
    first_name="John",
    last_name="Smith",
    email="john.smith@greenfield.com",
)

Output

User(
    id="624e5cc3-aaf3-4b35-a92b-352f0d102daf",
    first_name="John",
    last_name="Smith",
    email="john.smith@greenfield.com",
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
)

List users

This function allows you to list all users in your organisation.

Input

client.list_users()

Output

[
    User(
        id="624e5cc3-aaf3-4b35-a92b-352f0d102daf",
        first_name="John",
        last_name="Smith",
        email="john.smith@greenfield.com",
        created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
        created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
    ),
]

Get user

This function allows you to get a user by ID.

Input

client.get_user("624e5cc3-aaf3-4b35-a92b-352f0d102daf")

Output

User(
    id="624e5cc3-aaf3-4b35-a92b-352f0d102daf",
    first_name="John",
    last_name="Smith",
    email="john.smith@greenfield.com",
    created_at=datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=TzInfo(UTC)),
    created_by="51f9cf7c-78f5-49e0-b6fd-ba281346ae3f",
)

API key management

We recommend all users to automatically rotate their API keys on a regular basis. Users can also recover their API key via email.

Rotate API key

This function allows users to rotate their API key. This is useful to improve your organisation security by implementing an automated process that rotates API keys on a regular basis.

Input

client.rotate_api_key()

Output

RotateAPIKey(
    new_api_key="NzhjMWQwNjE1OGRhZDYxZDkxMWZlZjU1MWI1OTVlMmIK",
)

Recover API key

This function allows users to recover their API key via email. If we find a user with the email address provided, the user will receive an email with instructions to recover their API key. The link sent via email expires in 7 days. This function doesn’t require authentication.

Input

client.recover_api_key("jane.doe@example.com")

Output

RecoverAPIKey(
    message="Please follow the instructions sent via email.",
)