Purchasing Catalog Images

This page describes how to create an order for catalog images, complete the payment (automatically with credits or via Stripe), and manage existing orders through the API.

NOTE

All endpoints require a valid bearer token. See the Authentication page for details.


Create an Order

Creates a new order for one or more catalog images. The order will be in UNPAID status until payment is completed.

Endpoint

POST https://app.open-cosmos.com/api/data/v0/order/orders

Query Parameters

ParameterTypeRequiredDescription
redirect_hoststringNoOverride the default redirect URL after checkout completion.

Request Body

FieldTypeRequiredDescription
typestringYesMust be "IMAGE" for catalog purchases.
dataobjectYesContains the order_line_items array.
data.order_line_itemsarrayYesArray of items to purchase (see below).
organisationintegerYesYour organisation's numeric ID.
contract_idintegerYesThe contract ID for this purchase. See how to retrieve your contract ID.

Order Line Item fields:

FieldTypeRequiredDescription
collectionstringYesThe STAC collection ID.
itemstringYesThe STAC item ID.
levelstringYesThe processing level (e.g. L2A).
geometryobjectNoA GeoJSON geometry object for sub-area purchases. Must be paired with project_id.
project_idstringNoThe project ID to associate the item with. Must be paired with geometry.

NOTE

The geometry and project_id fields must either both be provided or both be omitted. You cannot set one without the other.

Response

The response includes the created order with a unique ID, status, computed prices for each line item, and a checkout URL if Stripe payment is required.

NOTE

If your organisation has sufficient credits, the order is paid immediately as part of this request. The response will show "status": "PAID" and "checkout_redirect_url": "". No separate checkout call is needed. See Payment with Credits below.

When credits cover the full order total (order already paid — no further action needed):

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "IMAGE",
    "customer_id": "cus-abc123",
    "organisation_id": 42,
    "contract_id": 456,
    "status": "PAID",
    "payment_method": "NOT_REQUIRED",
    "payment_reference_number": "",
    "order_line_items": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "collection": "sentinel-2-l2a",
        "item": "S2A_MSIL2A_20230615T100559_N0509_R022_T33UUP_20230615T135959",
        "level": "L2A",
        "geometry": null,
        "project_id": null,
        "price": {
          "value": "150.00",
          "discount": "0",
          "final": "150.00",
          "currency": "USD"
        },
        "is_downloaded": false,
        "delivered": false,
        "paid": true,
        "visualized_count": 0,
        "downloaded_count": 0
      }
    ],
    "checkout_redirect_url": "",
    "created_by": "google-apps|user@example.com",
    "created_at": "2026-01-21T12:00:00Z",
    "updated_at": "2026-01-21T12:00:00Z"
  }
}

When Stripe payment is required (credits insufficient — proceed to the checkout endpoint):

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "IMAGE",
    "customer_id": "cus-abc123",
    "organisation_id": 42,
    "contract_id": 456,
    "status": "UNPAID",
    "payment_method": "CARD",
    "payment_reference_number": "",
    "order_line_items": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "collection": "sentinel-2-l2a",
        "item": "S2A_MSIL2A_20230615T100559_N0509_R022_T33UUP_20230615T135959",
        "level": "L2A",
        "geometry": null,
        "project_id": null,
        "price": {
          "value": "150.00",
          "discount": "0",
          "final": "150.00",
          "currency": "USD"
        },
        "is_downloaded": false,
        "delivered": false,
        "paid": false,
        "visualized_count": 0,
        "downloaded_count": 0
      }
    ],
    "checkout_redirect_url": "https://checkout.stripe.com/c/pay/cs_test_...",
    "created_by": "google-apps|user@example.com",
    "created_at": "2026-01-21T12:00:00Z",
    "updated_at": "2026-01-21T12:00:00Z"
  }
}

Examples

Bash

curl --request POST "https://app.open-cosmos.com/api/data/v0/order/orders" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" \
--data-raw '{
  "type": "IMAGE",
  "data": {
    "order_line_items": [
      {
        "collection": "sentinel-2-l2a",
        "item": "S2A_MSIL2A_20230615T100559_N0509_R022_T33UUP_20230615T135959",
        "level": "L2A"
      }
    ]
  },
  "organisation": 42,
  "contract_id": 456
}'

  Python

import requests

# Get bearer token and add to session (see Authentication docs)

order_payload = {
    "type": "IMAGE",
    "data": {
        "order_line_items": [
            {
                "collection": "sentinel-2-l2a",
                "item": "S2A_MSIL2A_20230615T100559_N0509_R022_T33UUP_20230615T135959",
                "level": "L2A",
            }
        ]
    },
    "organisation": 42,
    "contract_id": 456,
}

response = session.post(
    "https://app.open-cosmos.com/api/data/v0/order/orders",
    json=order_payload,
)

order = response.json()
print(f"Order ID: {order['data']['id']}")
print(f"Status: {order['data']['status']}")
print(f"Checkout URL: {order['data']['checkout_redirect_url']}")

Purchasing Multiple Items in a Single Order

You can include multiple items in the order_line_items array to purchase them together in a single order:

order_payload = {
    "type": "IMAGE",
    "data": {
        "order_line_items": [
            {
                "collection": "sentinel-2-l2a",
                "item": "S2A_MSIL2A_20230615T100559_N0509_R022_T33UUP_20230615T135959",
                "level": "L2A",
            },
            {
                "collection": "sentinel-2-l2a",
                "item": "S2A_MSIL2A_20230620T101021_N0509_R022_T33UUP_20230620T140000",
                "level": "L2A",
            },
        ]
    },
    "organisation": 42,
    "contract_id": 456,
}

response = session.post(
    "https://app.open-cosmos.com/api/data/v0/order/orders",
    json=order_payload,
)

order = response.json()
for item in order["data"]["order_line_items"]:
    print(f"Item: {item['item']} — Price: {item['price']['final']} {item['price']['currency']}")

Payment with Credits

If your organisation has a credit balance, credits are always applied automatically during the POST /orders request itself. This applies to both UI and API purchases.

When credits fully cover the order

When your organisation's available credit balance is equal to or greater than the order total, the POST /orders request completes the purchase immediately — no Stripe session is created, no browser redirect is needed, and no separate checkout call is required. Credits are deducted and the order status is set to PAID within the same request.

The create order response will show "status": "PAID" and an empty "checkout_redirect_url":

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "PAID",
    "payment_method": "NOT_REQUIRED",
    "checkout_redirect_url": ""
  }
}

An empty checkout_redirect_url in the create response is not an error. It means the order has already been paid with credits and the purchased data is available.

TIP

For fully automated or M2M (machine-to-machine) workflows, ensure your organisation's credit balance covers expected orders. When it does, the entire purchase flow reduces to two API calls — create order, verify status — with no browser interaction required.

When credits do not fully cover the order

If your credit balance is insufficient to cover the full order total, the POST /orders response will contain a non-empty checkout_redirect_url pointing to a Stripe hosted checkout page, and the order status will be UNPAID. You must then call the checkout endpoint (see Initiate Stripe Checkout below) to obtain a fresh Stripe session URL and complete payment by card.

Credits and card payment cannot be combined on a single order — if credits do not fully cover the order, the full amount is payable via Stripe.

See the Credits user manual for details on credit balances, expiry, and top-ups.


Initiate Stripe Checkout

This section applies when the POST /orders response returned a non-empty checkout_redirect_url, meaning your organisation's credit balance does not cover the full order total. Call this endpoint to obtain a fresh Stripe checkout session URL where the user can complete payment by card.

If credits fully cover the order, the order is already PAID in the create response and this endpoint is not needed. If you call it anyway, it will return an empty checkout_url and leave the order status unchanged. See Payment with Credits above.

Endpoint

GET https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}/checkout

Path Parameters

ParameterTypeRequiredDescription
order_idUUIDYesThe order ID returned from the create order response.

Response

{
  "data": {
    "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_..."
  }
}

The checkout_url field will be:

  • Non-empty — redirect the user to this Stripe URL to complete card payment.
  • Empty string — the order has been paid with credits; no further action is required.

NOTE

If the order creation response already includes a checkout_redirect_url, you can use that URL directly without calling this endpoint separately. This endpoint is useful when you need to generate a new checkout session for an existing unpaid order.

Examples

Bash

ORDER_ID="550e8400-e29b-41d4-a716-446655440000"

CHECKOUT_RESPONSE=$(curl --request GET "https://app.open-cosmos.com/api/data/v0/order/orders/${ORDER_ID}/checkout" \
  --header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}")

CHECKOUT_URL=$(echo "$CHECKOUT_RESPONSE" | jq -r '.data.checkout_url')

if [ -z "$CHECKOUT_URL" ]; then
  echo "Order paid with credits — no Stripe checkout required."
else
  echo "Complete payment at: ${CHECKOUT_URL}"
fi

  Python

import requests

# Get bearer token and add to session (see Authentication docs)

order_id = "550e8400-e29b-41d4-a716-446655440000"

response = session.get(
    f"https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}/checkout",
)

checkout_url = response.json()["data"]["checkout_url"]

if not checkout_url:
    print("Order paid with credits — no Stripe checkout required.")
else:
    print(f"Complete payment at: {checkout_url}")

Get Order Details

Retrieves the full details of a specific order, including its current status and line items.

Endpoint

GET https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}

Path Parameters

ParameterTypeRequiredDescription
order_idUUIDYesThe ID of the order to retrieve.

Response

Returns a full OrderResponse object (same structure as the create order response).

Examples

Bash

ORDER_ID="550e8400-e29b-41d4-a716-446655440000"

curl --request GET "https://app.open-cosmos.com/api/data/v0/order/orders/${ORDER_ID}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}"

  Python

import requests

# Get bearer token and add to session (see Authentication docs)

order_id = "550e8400-e29b-41d4-a716-446655440000"

response = session.get(
    f"https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}",
)

order = response.json()
print(f"Order status: {order['data']['status']}")

Polling for Payment Completion

After redirecting the user to the Stripe checkout page, you can poll the order status to detect when payment has been completed. If the order was paid with credits, the status will already be PAID immediately after the checkout call and the first poll will return straight away:

import time
import requests

# Get bearer token and add to session (see Authentication docs)

order_id = "550e8400-e29b-41d4-a716-446655440000"
max_attempts = 30
poll_interval_seconds = 10

for attempt in range(max_attempts):
    response = session.get(
        f"https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}",
    )
    order = response.json()
    status = order["data"]["status"]

    if status == "PAID":
        print("Payment completed successfully!")
        break
    elif status == "CANCELLED":
        print("Order was cancelled.")
        break
    else:
        print(f"Status: {status} — waiting {poll_interval_seconds}s...")
        time.sleep(poll_interval_seconds)

List Orders

Retrieves a list of your orders, optionally filtered by order IDs.

Endpoint

GET https://app.open-cosmos.com/api/data/v0/order/orders

Query Parameters

ParameterTypeRequiredDescription
order_idUUID[]NoOne or more order IDs to filter by. Can be repeated for multiple IDs.

Response

Returns an array of OrderResponse objects.

Examples

Bash

# List all orders
curl --request GET "https://app.open-cosmos.com/api/data/v0/order/orders" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}"

# Filter by specific order IDs
curl --request GET "https://app.open-cosmos.com/api/data/v0/order/orders?order_id=550e8400-e29b-41d4-a716-446655440000&order_id=660f9500-f30c-52e5-b827-557766551111" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}"

  Python

import requests

# Get bearer token and add to session (see Authentication docs)

# List all orders
response = session.get(
    "https://app.open-cosmos.com/api/data/v0/order/orders",
)

orders = response.json()
for order in orders["data"]:
    print(f"Order {order['id']}: {order['status']}")

Update or Cancel an Order

Updates an existing order's status or payment method. This can be used to cancel an unpaid order or change the payment method.

Endpoint

PATCH https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}

Path Parameters

ParameterTypeRequiredDescription
order_idUUIDYesThe ID of the order to update.

Request Body

All fields are optional. Only include the fields you want to update.

FieldTypeDescription
statusstringNew status: UNPAID, PAID, or CANCELLED.
payment_methodstringNew payment method: BANK_TRANSFER, CARD, or NOT_REQUIRED.

Response

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Examples

Cancel an order (Bash)

ORDER_ID="550e8400-e29b-41d4-a716-446655440000"

curl --request PATCH "https://app.open-cosmos.com/api/data/v0/order/orders/${ORDER_ID}" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" \
--data-raw '{
  "status": "CANCELLED"
}'

  Cancel an order (Python)

import requests

# Get bearer token and add to session (see Authentication docs)

order_id = "550e8400-e29b-41d4-a716-446655440000"

response = session.patch(
    f"https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}",
    json={"status": "CANCELLED"},
)

result = response.json()
print(f"Order {result['data']['id']} has been cancelled.")

Error Responses

All ordering endpoints return errors in a consistent format:

{
  "errors": [
    {
      "type": "invalid_input",
      "message": "order line item must have collection, item and level",
      "field": "order_line_item",
      "source": "order-service",
      "trace_id": "abc123def456"
    }
  ]
}

Common Error Codes

HTTP StatusTypeDescription
400invalid_inputThe request body or parameters are invalid.
401unauthorizedMissing or invalid bearer token.
403forbiddenYou do not have permission to access this resource.
404not_foundThe specified order or item was not found.
500internal_errorAn unexpected server error occurred.

Where to Next

Pricing API | API Reference