Uploading your own data
Geopera isn’t only a place to buy imagery — you can bring your own rasters (COGs and other geospatial files) into a project, where they become first-class STAC items + assets: searchable, renderable, clippable, and analysable like any other data.
The upload is a session with four operations. You reserve quota, get a signed URL, push bytes straight to storage, then finalize — which ingests the file into items and assets and runs the post-upload pipeline.
| Step | Operation | Side-effect | Scope |
|---|---|---|---|
| Reserve a session | uploads.initiate | compute | uploads:write |
| Get a signed URL | uploads.signed_url | compute | uploads:write |
| (optional) report progress | uploads.progress | compute | uploads:write |
| Finalize → items + assets | uploads.complete | compute | uploads:write |
| Abandon | uploads.fail | compute | uploads:write |
Best practice — make it a COG. Render and analytics are fastest when the uploaded raster is a Cloud-Optimized GeoTIFF with overviews and embedded statistics. See the COG conventions referenced in Processing & analytics.
1. Initiate the session
uploads.initiate reserves storage quota up front (so a too-large upload fails fast
with 402 rather than half-landing) and requires editor/admin on the target project.
curl -s -X POST https://api.geopera.com/v1/op/uploads.initiate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "your-project-id",
"file_count": 1,
"total_bytes": 524288000,
"transfer_method": "signed_url",
"target_collection_id": "optional-collection-id"
}'{ "id": "u_8f12..." }Inputs: project_id (required), file_count, total_bytes (used for the quota
reservation), and optionally target_collection_id / target_item_id to ingest into
an existing collection or item, plus asset_key and is_categorical hints. The
returned id is the upload session id used by every later step.
If the org doesn’t have enough storage quota, this returns 402 Payment Required —
upgrade or free space before retrying.
2. Get a signed URL
uploads.signed_url mints a short-lived, presigned URL for one file. You upload the
bytes directly to object storage — they never pass through the API.
curl -s -X POST https://api.geopera.com/v1/op/uploads.signed_url \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "u_8f12...",
"file_name": "harbour_2024.tif",
"content_type": "image/tiff"
}'{
"upload_url": "https://storage.googleapis.com/...&X-Goog-Signature=...",
"object_path": "uploads/u_8f12.../harbour_2024.tif"
}3. Upload the bytes
PUT the file straight to upload_url:
curl -X PUT \
-H "Content-Type: image/tiff" \
--upload-file ./harbour_2024.tif \
"<upload_url>"For multi-file uploads, request one signed URL per file (repeat step 2) and PUT each.
Optionally report progress with uploads.progress (upload_id, status, bytes_uploaded) — useful to drive a progress bar or for resumable clients.
4. Complete the upload
uploads.complete finalizes the session: it creates the STAC item(s) and asset
record(s) for the uploaded file(s) and runs the post-upload pipeline (which derives
band names, statistics, and a default visualization profile so the data is immediately
renderable).
curl -s -X POST https://api.geopera.com/v1/op/uploads.complete \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "upload_id": "u_8f12..." }'{ "id": "u_8f12...", "item_ids": ["it_3a9c..."] }complete is a producer: each item it creates is recorded with a provenance edge
back to the upload session, in the same transaction (see Provenance & lineage). A completion that produces no
new item (already complete, metadata-only) is a legitimate no-op.
The new items now show up in items.search and can be rendered, clipped, and fed to
analytics like any other item.
5. Abandon a session
If something goes wrong client-side, release the reservation with uploads.fail:
curl -s -X POST https://api.geopera.com/v1/op/uploads.fail \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "u_8f12...",
"error_message": "client aborted",
"error_step": "transfer"
}'This marks the session failed and returns the reserved storage to your quota.