Storage
Manage object storage buckets, IAM users, container registries, and repositories.
Object Storage
List Buckets
GET /v1/projects/:projectId/storage/object-storageReturns buckets with S3 endpoint info, including CDN status (cdnEnabled, cdnDomain).
Auth: Required (member, scope: object-storage:read)
Preview Cost
GET /v1/projects/:projectId/storage/object-storage/preview?provider=<provider>&tier=<tier>Returns a prorated cost preview for a bucket tier, including the subtotal, estimated tax, any applied credit, and the total due today. The tier parameter defaults to STARTER.
Available tiers: STARTER (USD 5/mo), GROWTH (USD 20/mo), SCALE (USD 75/mo), ENTERPRISE (USD 300/mo).
Auth: Required (member)
Create Bucket
POST /v1/projects/:projectId/storage/object-storageProvisions an S3-compatible bucket on a prepaid tier. The prorated amount is charged to the project's payment method off-session and the response is { deployed: true }. If the project has no card, the request returns 402 — see Billing → Deploy Payment Errors.
Request Body:
{
"name": "my-bucket",
"provider": "AWS_S3",
"region": "eu-north-1",
"tier": "STARTER",
"publicAccess": false,
"versioning": false,
"cdnEnabled": false,
"corsRules": [
{
"allowedOrigins": ["https://example.com"],
"allowedMethods": ["GET", "PUT"],
"allowedHeaders": ["*"],
"exposeHeaders": ["ETag"],
"maxAgeSeconds": 3600
}
]
}Each tier includes storage, egress, and request allowances. Usage is monitored and the bucket is suspended when the tier limit is exceeded by 10% (grace buffer). Upgrade to a higher tier to resume.
corsRules is optional. Each rule requires at least one allowedOrigins entry (use "*" to allow any origin) and one allowedMethods entry (GET, PUT, POST, DELETE, HEAD). allowedHeaders, exposeHeaders, and maxAgeSeconds are optional.
cdnEnabled is optional (defaults to false) and serves the bucket's public objects through a global CDN for faster delivery worldwide. It requires publicAccess: true; CloudFront delivery is metered at USD 0.12/GB and USD 0.015 per 10K requests. It can also be toggled after creation — see Update CDN below.
Auth: Required (member, write access, scope: object-storage:write)
Get Usage
GET /v1/projects/:projectId/storage/object-storage/:bucketId/usageReturns current storage, egress, and request usage for a bucket, along with the tier allowances. Usage is fetched from CloudWatch metrics (may have up to 24h delay).
Auth: Required (member, scope: object-storage:read)
Preview Tier Change
GET /v1/projects/:projectId/storage/object-storage/:bucketId/plan-preview?tier=<tier>Returns what moving an existing bucket to tier costs right now: the prorated subtotal, estimated tax, any credit balance applied, and the total charged today, alongside the current and new monthly prices.
Upgrades return a positive dueToday, charged immediately by Change Tier. Downgrades charge nothing — the unused part of the current plan comes back as creditBack, added to the account balance and applied to future invoices.
Response:
{
"direction": "upgrade",
"subtotal": 4129,
"tax": 1032,
"creditApplied": 0,
"dueToday": 5161,
"creditBack": 0,
"currentMonthly": 2000,
"monthly": 7500,
"currency": "USD",
"nextBillingDate": "2026-08-25T00:00:00.000Z"
}All amounts are in minor units (cents). tax is null when the organization's tax location cannot be resolved yet.
Auth: Required (member, scope: object-storage:read)
Change Tier
PATCH /v1/projects/:projectId/storage/object-storage/:bucketId/tierChanges a bucket to a different tier. If the bucket was suspended due to exceeding its tier, upgrading reactivates it. Downgrades are allowed only when current usage fits within the target tier's limits.
The change takes effect immediately and is prorated to the billing cycle. Upgrades invoice the prorated difference against the payment method on file straight away — use Preview Tier Change to show the amount first. If the card is declined the request returns 402 and the tier is left unchanged.
Request Body:
{
"tier": "GROWTH"
}Auth: Required (member, write access, scope: object-storage:write)
Update CORS
PATCH /v1/projects/:projectId/storage/object-storage/:bucketId/corsReplaces the bucket's CORS (Cross-Origin Resource Sharing) configuration. The full rule set is applied at once — send the complete list, not a delta. An empty corsRules array clears the configuration.
Request Body:
{
"corsRules": [
{
"allowedOrigins": ["https://example.com", "https://app.example.com"],
"allowedMethods": ["GET", "PUT", "POST"],
"allowedHeaders": ["*"],
"exposeHeaders": ["ETag"],
"maxAgeSeconds": 3600
}
]
}Auth: Required (member, write access, scope: object-storage:write)
Update CDN
PATCH /v1/projects/:projectId/storage/object-storage/:bucketId/cdnEnables or disables the CDN add-on for a bucket. Enabling requires the bucket to already have publicAccess set to true — otherwise the request returns 400. Delivery is metered at USD 0.12/GB and USD 0.015 per 10K requests while the CDN is enabled.
Request Body:
{
"cdnEnabled": true
}Response Data:
{
"cdnEnabled": true,
"cdnDomain": "d1234abcd.cloudfront.net"
}cdnDomain is null while disabled or before the CDN has finished provisioning.
Auth: Required (member, write access, scope: object-storage:write)
Delete Bucket
DELETE /v1/projects/:projectId/storage/object-storage/:bucketIdDeletes the S3 bucket and removes the Stripe subscription item.
Auth: Required (member, write access, scope: object-storage:write)
List Credentials Reaching This Bucket
GET /v1/projects/:projectId/storage/object-storage/:bucketId/credentialsEvery AWS credential that can reach this bucket — the ones scoped to it and the project-wide ones, which reach it too. Same shape as the entries in IAM → List Credentials.
Auth: Required (member, scope: object-storage:read)
List IAM Users (legacy)
GET /v1/projects/:projectId/storage/object-storage/:bucketId/iam-usersAuth: Required (member)
Create IAM User (legacy)
POST /v1/projects/:projectId/storage/object-storage/:bucketId/iam-usersCreates an IAM user with an access key and secret. Credentials are returned once and cannot be retrieved again.
Prefer POST /v1/projects/:projectId/iam. Credentials created
here are bucket-only and fixed at the scope they were created with — they cannot
later be re-scoped or extended to the container registry.
Auth: Required (member, write access, scope: object-storage:write)
Delete IAM User (legacy)
DELETE /v1/projects/:projectId/storage/object-storage/:bucketId/iam-users/:iamUserNameAuth: Required (member, write access, scope: object-storage:write)
Container Registry
List Registries
GET /v1/projects/:projectId/storage/container-registryReturns registries with their repositories.
Auth: Required (member, scope: registry:read)
Get Registry
GET /v1/projects/:projectId/storage/container-registry/:idAuth: Required (member)
List Available Regions
GET /v1/projects/:projectId/storage/container-registry/regionsReturns the regions a Cosmoner registry can be created in, as
{ "value": "eu-north-1", "label": "Europe (Stockholm)" } objects.
Auth: Required (member)
Preview Cost
GET /v1/projects/:projectId/storage/container-registry/previewReturns a prorated cost preview for the registry's USD 1/month fee, including the subtotal, estimated tax, any applied credit, and the total due today. Storage and egress are metered after the fact and are not part of the preview — see Container Registry for those rates.
Auth: Required (member)
Create Registry
POST /v1/projects/:projectId/storage/container-registryCreates a managed Cosmoner registry: the subscription items for its USD 1/month
fee, metered storage, and metered egress, plus the settings every repository
added under it inherits. region must be one of the values returned by the
regions endpoint and is fixed for the life of the registry.
The prorated fee is charged to the project's payment method off-session and the
response is { deployed: true, id }. If the project has no card, the request
returns 402 — see Billing → Deploy Payment Errors.
{
"name": "my-registry",
"region": "eu-north-1",
"imageTagMutability": "MUTABLE",
"scanOnPush": true,
"encryptionType": "AES256",
"lifecycleEnabled": true,
"lifecycleKeepLastN": 10,
"lifecycleUntaggedAfterDays": 7
}| Field | Default | Notes |
|---|---|---|
imageTagMutability | MUTABLE | IMMUTABLE rejects a second push to an existing tag. |
scanOnPush | true | Scans each pushed image for known vulnerabilities. |
encryptionType | AES256 | KMS requires kmsKeyArn and cannot be changed for existing repositories. |
kmsKeyArn | — | Required when encryptionType is KMS. |
lifecycleEnabled | true | Expires old images automatically. |
lifecycleKeepLastN | 10 | 1–1000. Images beyond this count expire. |
lifecycleUntaggedAfterDays | 7 | 1–365. Untagged images expire after this many days. |
Auth: Required (member, write access, scope: registry:write)
Update Registry
PATCH /v1/projects/:projectId/storage/container-registry/:idUpdates a registry's settings. At least one field must be provided. Tag mutability and scanning are re-applied to every existing repository; encryption applies only to repositories created afterwards, because it is fixed when a repository is created.
Auth: Required (member, write access, scope: registry:write)
Delete Registry
DELETE /v1/projects/:projectId/storage/container-registry/:idDeletes every repository under the registry and stops its fee, storage, and
egress charges. Billing is stopped before anything is deleted: if that fails
the registry is kept and the request returns 502, so the deletion can be
retried without losing track of what is still being charged.
Auth: Required (member, write access, scope: registry:write)
List Repositories
GET /v1/projects/:projectId/storage/container-registry/:id/repositoriesAuth: Required (member)
Create Repository
POST /v1/projects/:projectId/storage/container-registry/:id/repositoriesAuth: Required (member, write access, scope: registry:write)
Request Body:
{
"name": "my-app",
"visibility": "PRIVATE",
"publicEgressCapGb": null
}Names must be lowercase and may contain dots, hyphens, and underscores.
| Field | Default | Notes |
|---|---|---|
visibility | PRIVATE | PUBLIC allows anonymous pulls. Pushes always require an API key. |
publicEgressCapGb | — | Required when visibility is PUBLIC. 1–10,000 GB of anonymous egress per billing period; the repository reverts to PRIVATE when it is reached. |
Get Repository
GET /v1/projects/:projectId/storage/container-registry/:id/repositories/:repoIdAuth: Required (member)
List Repository Credentials
GET /v1/projects/:projectId/storage/container-registry/:id/repositories/:repoId/credentialsEvery AWS credential that can reach this repository: those scoped to it, and the project-wide ones, which reach it too. Create and delete them through IAM.
Responds { "users": [...], "error": null }, with error set and users empty
when AWS could not be reached — the repository's own data still loads.
Auth: Required (member)
List Images
GET /v1/projects/:projectId/storage/container-registry/:id/repositories/:repoId/imagesReturns every image stored in the repository, newest push first. The list is read from the registry at request time rather than from a stored copy, so it reflects what a pull would find — including images the retention policy has already expired.
Auth: Required (member)
Response:
{
"digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"tags": ["latest", "v2"],
"sizeBytes": 74051584,
"pushedAt": "2026-08-02T10:14:00.000Z",
"lastPulledAt": "2026-08-04T08:00:00.000Z",
"scanStatus": "COMPLETE",
"scanFindings": { "HIGH": 2, "MEDIUM": 5 }
}tags is empty for an image superseded by a newer push of the same tag.
scanStatus and scanFindings are null unless the registry was created with
scan-on-push enabled and the scan has finished.
Delete Image
DELETE /v1/projects/:projectId/storage/container-registry/:id/repositories/:repoId/images/:digestDeletes one image manifest and every tag pointing at it. The digest is the
sha256:… value from the image list, URL-encoded; deleting by tag is not
supported, because a tag can move between listing an image and deleting it.
Auth: Required (member, write access, scope: registry:write)
Update Repository
PATCH /v1/projects/:projectId/storage/container-registry/:id/repositories/:repoIdUpdates the repository's retention policy. The policy is enforced by the registry itself, so expiry happens on the provider's schedule rather than at push time.
Auth: Required (member, write access, scope: registry:write)
Request Body:
{
"gcEnabled": true,
"gcKeepLastN": 10,
"gcDeleteUntagged": true,
"gcUntaggedAfterDays": 7,
"visibility": "PUBLIC",
"publicEgressCapGb": 50
}gcKeepLastN accepts 1–1000 and gcUntaggedAfterDays accepts 1–365. At least
one field must be provided.
visibility and publicEgressCapGb control anonymous pulls — see
Public repositories.
Switching to PUBLIC requires a cap, switching to PRIVATE clears it, and
either raising the cap or reopening a repository that hit it starts a fresh
allowance for the period.
Repositories are returned with publicEgressUsedGb, the anonymous egress
served in the current period, and publicEgressCapHitAt, set when a cap
closed the repository.
Delete Repository
DELETE /v1/projects/:projectId/storage/container-registry/:id/repositories/:repoIdAuth: Required (member, write access, scope: registry:write)