Delivery Bucket Setup
Set up a bucket to receive your data
How to create credentials on AWS, Google Cloud, or Azure, and the exact permissions to grant so we can deliver data to your own storage.
What we do with your bucket
We write files into your bucket. We also read back and list what we wrote, so we can confirm a delivery arrived complete. We do not delete your files. The permissions stay small, and every section below shows the smallest set that works.
- You create the bucket or container in your own cloud account. It stays yours.
- You create credentials that can write to it and read back what was written.
- You send them to us once. We encrypt them before storing them.
- We write files in, then check they all arrived, under the path prefix you chose.
If you would rather we could not see anything else in the bucket, point the permissions at one folder and tell us to use that folder as the path prefix. Each provider section shows how.
What you send us
Four things, whichever provider you use.
| Field | Required | What it means |
|---|---|---|
| Type | Yes | S3, GCS, or AZBLOB |
| Bucket | Yes | The bucket name, or the container name on Azure |
| Region | Yes | The region your bucket lives in |
| Path prefix | No | A folder we put every file under, for example pixxel/deliveries/ |
| Credentials | Yes | The secret values listed in your provider's section below |
Login methods we support
Pick one method per bucket. Send us only the fields for that method.
| Provider | Method | Notes |
|---|---|---|
| AWS S3 | IAM role we assume | Best choice. No secret leaves your account, nothing to rotate |
| AWS S3 | IAM user access key | Works. You have to rotate it yourself |
| AWS S3 | Temporary session credentials | Expires in hours. Good for a test only |
| Google Cloud Storage | Service account key file | The only method we support on Google Cloud |
| Azure Blob Storage | Entra service principal | Best choice. The only Azure option you can limit to one container |
| Azure Blob Storage | Storage account key | Works. Grants full access to the whole storage account |
| Azure Blob Storage | Connection string | Same full access as an account key |
AWS S3 — three methods
Option 1: IAM role
This is the option we recommend. You create a role in your AWS account and let our account assume it. No secret ever leaves your account, and there are no keys for you to rotate.
1. Create the permission policy
This is the smallest policy that works. Replace YOUR-BUCKET with your bucket name.
| Permission | Why we need it |
|---|---|
s3:PutObject | Writes the files. Big files upload in parts, and this covers every part |
s3:AbortMultipartUpload | Cleans up those parts when an upload fails partway. Without it, failed uploads leave hidden parts behind that you still pay for |
s3:GetObject | Reads back a delivered file to confirm it arrived whole |
s3:ListBucket | Lists what we delivered, so we can check nothing is missing |
Note the two separate statements. s3:ListBucket acts on the bucket itself, so its resource ends at the bucket name with no /*. Putting it on the /* resource is a common mistake, and it fails quietly.
To limit us to one folder, point the first statement at that folder and add a matching condition to the second.
IAM permission policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PixxelWriteAndReadBack",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:AbortMultipartUpload",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::YOUR-BUCKET/*"
},
{
"Sid": "PixxelListForDeliveryChecks",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::YOUR-BUCKET"
}
]
}Scoped to one folder:
{
"Sid": "PixxelWriteAndReadBack",
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:AbortMultipartUpload", "s3:GetObject"],
"Resource": "arn:aws:s3:::YOUR-BUCKET/pixxel/*"
},
{
"Sid": "PixxelListForDeliveryChecks",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::YOUR-BUCKET",
"Condition": {
"StringLike": {
"s3:prefix": "pixxel/*"
}
}
}Then tell us to use pixxel/ as the path prefix.
2. Create the trust policy
This says who is allowed to assume the role.
IAM trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::375894565286:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "YOUR-WORKSPACE-ID"
}
}
}
]
}Please keep the workspace ID check. It is the thing that makes sure only your own deliveries can use this role. sts:ExternalId is what AWS calls the field. The value that goes in it is your workspace ID.
3. Send us the details
| Field | Value | API name |
|---|---|---|
| Role ARN | The ARN of the role you just made | role_arn |
| Workspace ID | The same one you put in the trust policy | external_id |
Both are required. A role sent without a workspace ID will not be accepted.
If you use our API:
{
"name": "my-delivery-bucket",
"type": "S3",
"configs": {
"bucket": "YOUR-BUCKET",
"region": "us-west-2",
"path_prefix": "pixxel/"
},
"secrets": {
"role_arn": "arn:aws:iam::YOUR-ACCOUNT-ID:role/YourRoleName",
"external_id": "YOUR-WORKSPACE-ID"
}
}Option 2: IAM user access key
Create an IAM user, attach the same permission policy from step 1 (both statements), and create an access key for it.
| Field | Value | API name |
|---|---|---|
| Access key ID | Starts with AKIA | access_key_id |
| Secret access key | The secret shown once at creation | secret_access_key |
These keys never expire on their own, so rotation is on you. Read the note on rotation further down before you pick this option.
Option 3: Temporary session credentials
If your security team issues short-lived credentials from AWS STS, we accept those too.
| Field | Value | API name |
|---|---|---|
| Access key ID | Starts with ASIA | access_key_id |
| Secret access key | The matching secret | secret_access_key |
| Session token | The session token | session_token |
These stop working when the session expires, usually within a few hours. Deliveries start failing at that point. Use this for a short test only.
If your bucket uses KMS encryption
A bucket encrypted with a customer managed KMS key needs extra permissions, or writes are denied. Add these to the key policy, or to the role or user policy.
A bucket using the default S3 managed encryption needs nothing extra.
KMS statement:
{
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Encrypt"
],
"Resource": "arn:aws:kms:REGION:YOUR-ACCOUNT-ID:key/YOUR-KEY-ID"
}Google Cloud Storage — one method
We support one login method: a service account key file.
1. Create a service account
gcloud iam service-accounts create pixxel-delivery \
--display-name="Pixxel delivery"2. Grant it access to your bucket
Grant on the bucket, not the whole project. That keeps the access narrow.
gcloud storage buckets add-iam-policy-binding gs://YOUR-BUCKET \
--member="serviceAccount:pixxel-delivery@YOUR-PROJECT.iam.gserviceaccount.com" \
--role="roles/storage.objectUser"| Role | What it allows |
|---|---|
roles/storage.objectUser | Create, read, list, and replace. One role that covers everything, and what we suggest |
roles/storage.objectCreator plus roles/storage.objectViewer | The same, minus replace. Use this pair if you never want an existing file overwritten |
The second option cannot overwrite. If a delivery ever rewrites a file at the same path, it fails with a permission error. If you expect repeat deliveries to the same paths, use objectUser.
roles/storage.objectCreator on its own is not enough any more. It cannot read or list, so we would have no way to confirm a delivery finished.
For a custom role, these are the permissions we need:
| Permission | Why we need it |
|---|---|
storage.objects.create | Writes the files |
storage.objects.get | Reads back a delivered file to confirm it arrived whole |
storage.objects.list | Lists what we delivered, so we can check nothing is missing |
storage.objects.delete | Only needed so a delivery can replace a file at a path that already exists. Google counts an overwrite as a delete |
Grant these on the bucket, or on a single folder if you would rather keep us to one.
3. Create a key
gcloud iam service-accounts keys create key.json \
--iam-account=pixxel-delivery@YOUR-PROJECT.iam.gserviceaccount.com4. Send us the details
| Field | Value | API name |
|---|---|---|
| Project ID | Your Google Cloud project ID | project_id |
| Service account key | The full contents of key.json | service_account_credentials |
Send the whole file contents. Not a file path, and not just the private key.
We ask Google only for read and write access to storage. We cannot use the key for anything else in your project.
Azure Blob Storage — three methods
We try these in the order below, so send us only the fields for the one you picked.
Option 1: Entra service principal
This is the option we recommend. It is the only Azure method you can limit to a single container. The other two give access to the whole storage account.
1. Create an app registration and service principal
az ad app create --display-name pixxel-delivery
az ad sp create --id <app-id>2. Create a client secret
az ad app credential reset --id <app-id>Note the secret value now. Azure shows it only once.
3. Grant access to the container
Set the scope to the container, not the storage account, so the access stays narrow.
az role assignment create \
--assignee <app-id> \
--role "Storage Blob Data Contributor" \
--scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<account-name>/blobServices/default/containers/<container-name>"Storage Blob Data Contributor covers what we do, reading back and listing included. For a custom role instead, these are the data actions we need:
Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read
Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write
Microsoft.Storage/storageAccounts/blobServices/containers/blobs/add/actionThe read action covers both reading a blob back and listing the container.
4. Send us the details
| Field | Value | API name |
|---|---|---|
| Account name | Your storage account name | account_name |
| Tenant ID | Your Entra tenant ID | tenant_id |
| Client ID | The app registration ID | client_id |
| Client secret | The secret from step 2 | client_secret |
Client secrets expire, and Azure sets a lifetime when you create one. Put a reminder in your calendar, and read the note on rotation below.
Option 2: Storage account key
| Field | Value | API name |
|---|---|---|
| Account name | Your storage account name | account_name |
| Account key | Either of the two account keys | account_key |
Option 3: Connection string
| Field | Value | API name |
|---|---|---|
| Connection string | Azure Portal, then Storage account, then Access keys | connection_string |
A connection string contains an account key, so it carries the same full access as option 2. The same warning applies.
Things worth knowing
Saving credentials does not test your bucket
When you save a delivery bucket with us, we check that your credentials are real and well formed. We do not write a test file at that moment.
So a bucket can save cleanly and still fail on the first delivery, usually because a permission is missing or the bucket name is wrong. If that happens, check the permission policy first. We suggest running one small delivery right after setup to confirm the whole path works.
Credentials cannot be edited later
A delivery bucket is fixed once created. You can rename it and change its labels, but you cannot change the bucket, the region, or the credentials.
To rotate a key or a client secret, create a new delivery bucket with the new credentials, switch your deliveries over to it, then delete the old one. This is one more reason to prefer the AWS IAM role and the Azure service principal. With an IAM role there is nothing to rotate at all.
Paths and overwrites
Every file we write goes under the path prefix you gave us. Set the prefix to pixxel/ and a file named scene.tif lands at pixxel/scene.tif.
If a delivery writes to a path that already holds a file, the old file is replaced. If you do not want that, use a different path prefix for each delivery, or turn on object versioning on your bucket.
Your credentials are encrypted
We encrypt your credentials before storing them. They are never returned by our API, never shown in our interface, and are only decrypted at the moment a delivery runs.
Before you tell us you are ready
- The bucket or container exists, and I know its exact name and region.
- The credentials can write to that bucket, and read and list what was written.
- AWS role only: the trust policy names the Pixxel account ID and my workspace ID.
- AWS with a customer managed KMS key: the key permissions are added.
- Google Cloud: the key file says
"type": "service_account". - Azure: I picked one method and am sending only those fields.
- I noted any expiry date, so I can plan a swap before it lapses.
If a delivery fails, send us the error message. It tells us which side the problem is on, and it usually points straight at the missing permission.