End-to-End Ordering Quickstart
This guide walks you through the complete workflow for purchasing satellite imagery via the DataCosmos API — from authentication to downloading your data. Each step builds on the previous one, showing how data flows through the entire process.
By the end of this guide you will have:
- Authenticated and obtained an access token
- Searched the STAC catalogue for satellite imagery
- Retrieved pricing for the selected item
- Placed an order and completed checkout
- Downloaded the purchased data
NOTE
A complete runnable script combining all steps is provided at the end of this page.
Prerequisites
Before you begin, make sure you have:
- A valid token (JWT) — To get API credentials follow the steps described in the Authentication page.
- Organisation ID — Your organisation's numeric identifier (associated with your account).
- Contract ID — The contract under which purchases are made. Retrieve your contracts with:
Pick thecurl "https://app.open-cosmos.com/api/data/v1/dpap/organisations/{organisation_id}/policies" \ --header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}"contract_idfrom the returned array — the default contract has"default_contract": true.
For Bash examples: curl and jq must be installed.
For Python examples: Install the requests library:
pip install requests
Step 1: Authenticate
All DataCosmos API calls require a bearer token. Obtain one by following the steps described in the Authentication page.
Bash
# Authenticate and store the access token
DATACOSMOS_ACCESS_TOKEN="<your access token here>"
Python
import requests
access_token = "<your access token here>"
session = requests.Session()
session.headers.update(
{"Authorization": f'Bearer {access_token}'}
)
print("Authentication configuration completed.")
NOTE
The Python examples use a requests.Session object that automatically includes the bearer token in all subsequent requests. For full details see the Authentication page.
Step 2: Search the Catalogue
Use the STAC Search API to find satellite images matching your area of interest. This example searches for Sentinel-2 L2A images over a bounding box in Chile with less than 20% cloud cover.
Passing contract_id and project in the request body causes the API to embed pricing (opencosmos:price and opencosmos:price_currency) directly in each returned item, so you can skip the separate pricing step.
Endpoint
POST https://app.open-cosmos.com/api/data/v0/stac/search
Bash
# Search for Sentinel-2 images with low cloud cover in a bounding box
# contract_id and project are included in the body to get prices in results
SEARCH_RESULTS=$(curl --request POST "https://app.open-cosmos.com/api/data/v0/stac/search" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" \
--data-raw '{
"collections": ["sentinel-2-l2a"],
"bbox": [-71.72, -27.68, -71.02, -27.06],
"limit": 5,
"contract_id": YOUR_CONTRACT_ID,
"project": "YOUR_PROJECT_ID",
"query": {
"eo:cloudcover": {
"lte": 20
}
}
}')
# Extract the first result's collection, item ID, and price
COLLECTION=$(echo "$SEARCH_RESULTS" | jq -r '.features[0].collection')
ITEM_ID=$(echo "$SEARCH_RESULTS" | jq -r '.features[0].id')
PRICE=$(echo "$SEARCH_RESULTS" | jq -r '.features[0].properties["opencosmos:price"]')
CURRENCY=$(echo "$SEARCH_RESULTS" | jq -r '.features[0].properties["opencosmos:price_currency"]')
echo "Found item: ${COLLECTION} / ${ITEM_ID} (${PRICE} ${CURRENCY})"
Python
# Search for Sentinel-2 images with low cloud cover
# contract_id and project are included in the body to get prices in results
search_body = {
"collections": ["sentinel-2-l2a"],
"bbox": [-71.72, -27.68, -71.02, -27.06],
"limit": 5,
"contract_id": YOUR_CONTRACT_ID, # Replace with your contract ID
"project": "YOUR_PROJECT_ID", # Replace with your project ID
"query": {
"eo:cloudcover": {
"lte": 20,
},
},
}
response = session.post(
"https://app.open-cosmos.com/api/data/v0/stac/search",
json=search_body,
)
search_results = response.json()
features = search_results.get("features", [])
if not features:
print("No results found. Try adjusting your search parameters.")
else:
# Pick the first result
selected_item = features[0]
collection = selected_item["collection"]
item_id = selected_item["id"]
price = selected_item["properties"].get("opencosmos:price")
currency = selected_item["properties"].get("opencosmos:price_currency")
print(f"Found {len(features)} result(s). Selected: {collection} / {item_id} ({price} {currency})")
NOTE
The bbox parameter uses the format [west, south, east, north] in decimal degrees. The query parameter supports CQL filters such as eo:cloudcover. See the STAC Search API for all available search parameters.
Step 3: Retrieve Pricing
NOTE
If you passed contract_id in the search request (Step 2), the price is already available as opencosmos:price and opencosmos:price_currency on each returned item. You can skip this step and use those values directly.
If you need to fetch the price separately — for example when pricing a specific sub-area geometry — call the dedicated pricing endpoint. The contract_id is required.
Endpoint
GET https://app.open-cosmos.com/api/data/v0/order/price
Bash
PRICE_RESPONSE=$(curl --request GET \
"https://app.open-cosmos.com/api/data/v0/order/price?collection=${COLLECTION}&item=${ITEM_ID}&contract_id=YOUR_CONTRACT_ID" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}")
FINAL_PRICE=$(echo "$PRICE_RESPONSE" | jq -r '.data.final')
CURRENCY=$(echo "$PRICE_RESPONSE" | jq -r '.data.currency')
echo "Price: ${FINAL_PRICE} ${CURRENCY}"
Python
response = session.get(
"https://app.open-cosmos.com/api/data/v0/order/price",
params={
"collection": collection,
"item": item_id,
"contract_id": YOUR_CONTRACT_ID, # Replace with your contract ID
},
)
price = response.json()["data"]
print(f"Price: {price['final']} {price['currency']}")
NOTE
See the Pricing API page for full parameter details, including sub-area pricing with a custom geometry, and batch pricing for multiple items.
Step 4: Place an Order
Create an order for the selected item. If your organisation has sufficient credits, the order is paid automatically inside the create request and no further steps are needed. If credits do not cover the full amount, a Stripe checkout URL is returned and you must complete payment via the checkout endpoint.
4a. Create the Order
Endpoint
POST https://app.open-cosmos.com/api/data/v0/order/orders
Bash
# Create an order for the selected item
ORDER_RESPONSE=$(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\": \"${COLLECTION}\",
\"item\": \"${ITEM_ID}\",
\"level\": \"L2A\"
}
]
},
\"organisation\": YOUR_ORGANISATION_ID,
\"contract_id\": YOUR_CONTRACT_ID
}")
ORDER_ID=$(echo "$ORDER_RESPONSE" | jq -r '.data.id')
ORDER_STATUS=$(echo "$ORDER_RESPONSE" | jq -r '.data.status')
echo "Order created: ${ORDER_ID} (status: ${ORDER_STATUS})"
Python
# Create an order for the selected item
order_payload = {
"type": "IMAGE",
"data": {
"order_line_items": [
{
"collection": collection,
"item": item_id,
"level": "L2A",
}
]
},
"organisation": YOUR_ORGANISATION_ID, # Replace with your organisation ID
"contract_id": YOUR_CONTRACT_ID, # Replace with your contract ID
}
response = session.post(
"https://app.open-cosmos.com/api/data/v0/order/orders",
json=order_payload,
)
order = response.json()["data"]
order_id = order["id"]
print(f"Order created: {order_id} (status: {order['status']})")
NOTE
Check the create response before proceeding:
- If
statusis"PAID"andcheckout_redirect_urlis""— your organisation's credits covered the order. It is already paid. Skip Step 4b and go directly to Step 5. - If
statusis"UNPAID"andcheckout_redirect_urlis non-empty — credits were insufficient. Proceed to Step 4b to complete payment via Stripe.
4b. Initiate Stripe Checkout (only if Step 4a returned an unpaid order)
This step is only needed when the create order response returned a non-empty checkout_redirect_url, meaning credits did not cover the full order total and a Stripe payment is required. Skip this step if the order is already PAID.
The checkout_url in the response tells you which path applies:
- Empty string — the order has been paid with credits; proceed directly to Step 5.
- Non-empty URL — direct the user to this Stripe checkout page to complete card payment.
Endpoint
GET https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}/checkout
Bash
# Get the checkout URL
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 completed with credits — no Stripe checkout required."
else
echo "Complete payment at: ${CHECKOUT_URL}"
fi
Python
# Get the checkout URL
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 completed with credits — no Stripe checkout required.")
else:
print(f"Complete payment at: {checkout_url}")
NOTE
See Purchasing Catalog Images for full details on the create order response, both payment paths, and order management.
Step 5: Download Data
After completing payment, poll the order status until it changes to PAID, then download the assets from the STAC item.
5a. Wait for Payment Confirmation
NOTE
If the order was paid with credits in Step 4b, the status is already PAID and the first poll will return immediately. No waiting is needed in that case.
Bash
# Poll until the order is paid
MAX_ATTEMPTS=30
POLL_INTERVAL=10
for i in $(seq 1 $MAX_ATTEMPTS); do
STATUS=$(curl --silent --request GET \
"https://app.open-cosmos.com/api/data/v0/order/orders/${ORDER_ID}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" | jq -r '.data.status')
echo "Attempt ${i}: status = ${STATUS}"
if [ "$STATUS" = "PAID" ]; then
echo "Payment confirmed!"
break
elif [ "$STATUS" = "CANCELLED" ]; then
echo "Order was cancelled."
break
fi
sleep $POLL_INTERVAL
done
Python
import time
# Poll until the order is paid
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}",
)
status = response.json()["data"]["status"]
if status == "PAID":
print("Payment confirmed!")
break
elif status == "CANCELLED":
print("Order was cancelled.")
break
else:
print(f"Attempt {attempt + 1}: status = {status} — waiting {poll_interval_seconds}s...")
time.sleep(poll_interval_seconds)
5b. Download Assets
Once the order is paid, retrieve the STAC item and download all associated assets.
Bash
# Download all assets from the purchased item
ITEM_RESPONSE=$(curl --silent --request GET \
"https://app.open-cosmos.com/api/data/v0/stac/collections/${COLLECTION}/items/${ITEM_ID}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}")
# Download each asset
echo "$ITEM_RESPONSE" | jq -r '.assets | to_entries[] | "\(.value.title)\t\(.value.href)"' | while IFS=$'\t' read -r title href; do
echo "Downloading: ${title}"
curl --silent --output "${title}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" \
"${href}"
done
echo "All assets downloaded."
Python
import os
# Retrieve the STAC item to get asset download links
response = session.get(
f"https://app.open-cosmos.com/api/data/v0/stac/collections/{collection}/items/{item_id}",
)
item_data = response.json()
# Download each asset
for asset_key, asset in item_data["assets"].items():
filename = asset.get("title", asset_key)
print(f"Downloading: {filename}")
asset_response = session.get(asset["href"])
with open(filename, "wb") as fp:
fp.write(asset_response.content)
print("All assets downloaded.")
Complete End-to-End Scripts
Below are complete, runnable scripts that combine all five steps into a single workflow.
Python
"""
DataCosmos End-to-End Ordering Example
Prerequisites:
- pip install requests
- data_cosmos_api_credentials.json in the same directory
- Replace YOUR_ORGANISATION_ID and YOUR_CONTRACT_ID with real values
"""
import json
import os
import time
import requests
# ──────────────────────────────────────────────
# Configuration
# ──────────────────────────────────────────────
CREDENTIALS_FILE = "data_cosmos_api_credentials.json"
ORGANISATION_ID = 42 # Replace with your organisation ID
CONTRACT_ID = 456 # Replace with your contract ID
DOWNLOAD_DIR = "downloads"
# ──────────────────────────────────────────────
# Step 1: Authenticate
# ──────────────────────────────────────────────
print("Step 1: Authenticating...")
with open(CREDENTIALS_FILE) as fp:
oauth_body = json.load(fp)
session = requests.Session()
token_response = session.post(
"https://login.open-cosmos.com/oauth/token",
data=oauth_body,
).json()
session.headers.update(
{"Authorization": f'{token_response["token_type"]} {token_response["access_token"]}'}
)
print(" Authenticated successfully.\n")
# ──────────────────────────────────────────────
# Step 2: Search the Catalogue
# ──────────────────────────────────────────────
print("Step 2: Searching the catalogue...")
search_body = {
"collections": ["sentinel-2-l2a"],
"bbox": [-71.72, -27.68, -71.02, -27.06],
"limit": 5,
"query": {
"eo:cloudcover": {
"lte": 20,
},
},
}
response = session.post(
"https://app.open-cosmos.com/api/data/v0/stac/search",
json=search_body,
)
response.raise_for_status()
features = response.json().get("features", [])
if not features:
print(" No results found. Try adjusting your search parameters.")
exit(1)
selected_item = features[0]
collection = selected_item["collection"]
item_id = selected_item["id"]
print(f" Found {len(features)} result(s). Selected: {collection} / {item_id}\n")
# ──────────────────────────────────────────────
# Step 3: Retrieve Pricing
# ──────────────────────────────────────────────
print("Step 3: Retrieving pricing...")
response = session.get(
"https://app.open-cosmos.com/api/data/v0/order/price",
params={"collection": collection, "item": item_id, "contract_id": CONTRACT_ID},
)
response.raise_for_status()
price_data = response.json()["data"]
print(f" Price: {price_data['final']} {price_data['currency']}\n")
# ──────────────────────────────────────────────
# Step 4: Place an Order
# ──────────────────────────────────────────────
print("Step 4: Creating order...")
order_payload = {
"type": "IMAGE",
"data": {
"order_line_items": [
{
"collection": collection,
"item": item_id,
"level": "L2A",
}
]
},
"organisation": ORGANISATION_ID,
"contract_id": CONTRACT_ID,
}
response = session.post(
"https://app.open-cosmos.com/api/data/v0/order/orders",
json=order_payload,
)
response.raise_for_status()
order = response.json()["data"]
order_id = order["id"]
print(f" Order created: {order_id} (status: {order['status']})")
# Get checkout URL (empty means order was paid with credits)
response = session.get(
f"https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}/checkout",
)
response.raise_for_status()
checkout_url = response.json()["data"]["checkout_url"]
if not checkout_url:
print(" Order completed with credits — no Stripe checkout required.\n")
else:
print(f" Complete payment at: {checkout_url}\n")
# ──────────────────────────────────────────────
# Step 5: Wait for Payment and Download Data
# ──────────────────────────────────────────────
print("Step 5: Waiting for payment confirmation...")
max_attempts = 30
poll_interval = 10
for attempt in range(max_attempts):
response = session.get(
f"https://app.open-cosmos.com/api/data/v0/order/orders/{order_id}",
)
status = response.json()["data"]["status"]
if status == "PAID":
print(" Payment confirmed!\n")
break
elif status == "CANCELLED":
print(" Order was cancelled.")
exit(1)
else:
print(f" Attempt {attempt + 1}: status = {status} — waiting {poll_interval}s...")
time.sleep(poll_interval)
# Download assets
print("Downloading assets...")
response = session.get(
f"https://app.open-cosmos.com/api/data/v0/stac/collections/{collection}/items/{item_id}",
)
response.raise_for_status()
item_data = response.json()
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
for asset_key, asset in item_data["assets"].items():
filename = asset.get("title", asset_key)
filepath = os.path.join(DOWNLOAD_DIR, filename)
print(f" Downloading: {filename}")
asset_response = session.get(asset["href"])
with open(filepath, "wb") as fp:
fp.write(asset_response.content)
print(f"\nDone! All assets saved to '{DOWNLOAD_DIR}/'.")
Bash
#!/usr/bin/env bash
#
# DataCosmos End-to-End Ordering Example
#
# Prerequisites:
# - curl and jq installed
# - data_cosmos_api_credentials.json in the same directory
# - Replace YOUR_ORGANISATION_ID and YOUR_CONTRACT_ID below
#
set -euo pipefail
# ──────────────────────────────────────────────
# Configuration
# ──────────────────────────────────────────────
CREDENTIALS_FILE="data_cosmos_api_credentials.json"
ORGANISATION_ID=42 # Replace with your organisation ID
CONTRACT_ID=456 # Replace with your contract ID
DOWNLOAD_DIR="downloads"
# ──────────────────────────────────────────────
# Step 1: Authenticate
# ──────────────────────────────────────────────
echo "Step 1: Authenticating..."
DATACOSMOS_ACCESS_TOKEN=$(curl --silent --request POST "https://login.open-cosmos.com/oauth/token" \
--header "Content-Type: application/json" \
-d @"${CREDENTIALS_FILE}" | jq -r ".access_token")
echo " Authenticated successfully."
echo ""
# ──────────────────────────────────────────────
# Step 2: Search the Catalogue
# ──────────────────────────────────────────────
echo "Step 2: Searching the catalogue..."
SEARCH_RESULTS=$(curl --silent --request POST "https://app.open-cosmos.com/api/data/v0/stac/search" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" \
--data-raw '{
"collections": ["sentinel-2-l2a"],
"bbox": [-71.72, -27.68, -71.02, -27.06],
"limit": 5,
"query": {
"eo:cloudcover": {
"lte": 20
}
}
}')
RESULT_COUNT=$(echo "$SEARCH_RESULTS" | jq '.features | length')
COLLECTION=$(echo "$SEARCH_RESULTS" | jq -r '.features[0].collection')
ITEM_ID=$(echo "$SEARCH_RESULTS" | jq -r '.features[0].id')
echo " Found ${RESULT_COUNT} result(s). Selected: ${COLLECTION} / ${ITEM_ID}"
echo ""
# ──────────────────────────────────────────────
# Step 3: Retrieve Pricing
# ──────────────────────────────────────────────
echo "Step 3: Retrieving pricing..."
PRICE_RESPONSE=$(curl --silent --request GET \
"https://app.open-cosmos.com/api/data/v0/order/price?collection=${COLLECTION}&item=${ITEM_ID}&contract_id=${CONTRACT_ID}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}")
FINAL_PRICE=$(echo "$PRICE_RESPONSE" | jq -r '.data.final')
CURRENCY=$(echo "$PRICE_RESPONSE" | jq -r '.data.currency')
echo " Price: ${FINAL_PRICE} ${CURRENCY}"
echo ""
# ──────────────────────────────────────────────
# Step 4: Place an Order
# ──────────────────────────────────────────────
echo "Step 4: Creating order..."
ORDER_RESPONSE=$(curl --silent --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\": \"${COLLECTION}\",
\"item\": \"${ITEM_ID}\",
\"level\": \"L2A\"
}
]
},
\"organisation\": ${ORGANISATION_ID},
\"contract_id\": ${CONTRACT_ID}
}")
ORDER_ID=$(echo "$ORDER_RESPONSE" | jq -r '.data.id')
ORDER_STATUS=$(echo "$ORDER_RESPONSE" | jq -r '.data.status')
echo " Order created: ${ORDER_ID} (status: ${ORDER_STATUS})"
CHECKOUT_RESPONSE=$(curl --silent --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 completed with credits — no Stripe checkout required."
else
echo " Complete payment at: ${CHECKOUT_URL}"
fi
echo ""
# ──────────────────────────────────────────────
# Step 5: Wait for Payment and Download Data
# ──────────────────────────────────────────────
echo "Step 5: Waiting for payment confirmation..."
MAX_ATTEMPTS=30
POLL_INTERVAL=10
for i in $(seq 1 $MAX_ATTEMPTS); do
STATUS=$(curl --silent --request GET \
"https://app.open-cosmos.com/api/data/v0/order/orders/${ORDER_ID}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" | jq -r '.data.status')
if [ "$STATUS" = "PAID" ]; then
echo " Payment confirmed!"
break
elif [ "$STATUS" = "CANCELLED" ]; then
echo " Order was cancelled."
exit 1
fi
echo " Attempt ${i}: status = ${STATUS} — waiting ${POLL_INTERVAL}s..."
sleep $POLL_INTERVAL
done
echo ""
echo "Downloading assets..."
mkdir -p "${DOWNLOAD_DIR}"
ITEM_RESPONSE=$(curl --silent --request GET \
"https://app.open-cosmos.com/api/data/v0/stac/collections/${COLLECTION}/items/${ITEM_ID}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}")
echo "$ITEM_RESPONSE" | jq -r '.assets | to_entries[] | "\(.value.title)\t\(.value.href)"' | while IFS=$'\t' read -r title href; do
echo " Downloading: ${title}"
curl --silent --output "${DOWNLOAD_DIR}/${title}" \
--header "Authorization: Bearer ${DATACOSMOS_ACCESS_TOKEN}" \
"${href}"
done
echo ""
echo "Done! All assets saved to '${DOWNLOAD_DIR}/'."
Where to Next
For more detail on each step, see these pages:
- Authentication — Token management, OAuthlib examples, and credential setup.
- DataCosmos Developer Center — Full STAC search guide, project items, and advanced queries.
- Pricing API — Single item and batch pricing, sub-area geometry pricing, and response details.
- Purchasing Catalog Images — Multi-item orders, order updates, cancellation, and error handling.
- Ordering API Reference — Full OpenAPI specification for all ordering endpoints.