Cosmoner Docs
API Reference

Storage

Object storage buckets and container registries. **Buckets** are S3-compatible: any S3 client or SDK reads and writes them, with an access key issued under [IAM](/docs/api/iam). Each bucket runs on a prepaid tier that includes a storage, egress and request allowance, and its usage is watched against it — a bucket that goes more than 10% over any part of its allowance is suspended until it moves to a larger tier. See [Object Storage](/docs/guides/object-storage) and [Object storage pricing](/docs/pricing/object-storage). **Container registries** hold the images apps deploy from. Push and pull with `docker`, logging in with a Cosmoner API key that carries `registry:read` to pull and `registry:write` to push; see [Container Registry](/docs/guides/container-registry) and [Container registry pricing](/docs/pricing/container-registry). Repository names are unique across the project, not just within a registry. A repository is private by default. A public one can be pulled by anyone without credentials, and must carry a monthly cap on that anonymous egress (`publicEgressCapGb`, 1–10,000 GB), because it is billed to the project; when the cap is reached the repository becomes private again and `publicEgressCapHitAt` records when. Making it public again, or raising the cap, starts a fresh allowance. Several calls work with an API key but are not described operation by operation yet, because they answer with the resource's full internal record, which is not a stable contract: listing buckets (`GET /v1/projects/{projectId}/storage/object-storage`, `object-storage:read`), listing registries with their repositories (`GET /v1/projects/{projectId}/storage/container-registry`, `registry:read`), reading one (`GET …/container-registry/{id}`), listing and reading its repositories (`GET …/container-registry/{id}/repositories` and `…/repositories/{repoId}`), creating a repository (`POST …/container-registry/{id}/repositories` with `name` and optionally `visibility` and `publicEgressCapGb`, `registry:write`) and updating a repository's retention and visibility (`PATCH …/repositories/{repoId}`, `registry:write`). Rely only on `id`, `name`, `region`, `status`, `tier`, `publicAccess`, `publicPaths`, `versioning`, `corsRules`, `cdnEnabled`, `cdnDomain` and `createdAt` for a bucket; `id`, `name`, `region`, `status` and `createdAt` for a registry; and `id`, `name`, `fullPath`, `visibility`, `publicEgressCapGb`, `publicEgressUsedGb`, `publicEgressCapHitAt`, the `gc…` retention settings and `createdAt` for a repository — and expect the rest to change. Creating and updating a repository are refused to members with the viewer role, and to a project blocked over an unpaid invoice. Creating a bucket or a registry, previewing what one costs, changing a registry's settings, turning a bucket's CDN on or off, browsing a bucket's files and seeing which access keys reach a bucket or repository are done in the control panel for now.

DELETE
/v1/projects/{projectId}/storage/container-registry/{id}

Deletes every repository in the registry, and every image in them, and stops the registry's monthly fee and its storage and egress charges. Metered usage already recorded in the current period is still invoiced. This cannot be undone.

Billing is stopped before anything is deleted. If that fails, or the repositories cannot be removed, the call answers 502 and the registry is kept, so the deletion can be retried.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of registry:read and registry:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
id*string

The registry's id.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X DELETE "https://example.com/v1/projects/string/storage/container-registry/string"
{  "success": true,  "data": {}}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
DELETE
/v1/projects/{projectId}/storage/container-registry/{id}/repositories/{repoId}

Deletes the repository and every image in it, without touching the rest of the registry. This cannot be undone.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of registry:read and registry:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
id*string

The registry's id.

Length1 <= length
repoId*string

The repository's id.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X DELETE "https://example.com/v1/projects/string/storage/container-registry/string/repositories/string"
Empty
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
GET
/v1/projects/{projectId}/storage/container-registry/{id}/repositories/{repoId}/images

Every image stored in the repository, newest push first where the registry records push times. The list is read from the registry at the time of the request rather than from a stored copy, so it shows what a pull would find — including images due to expire under the retention policy that the registry has not removed yet.

Several tags pointing at one image are listed as one entry. A repository nothing has been pushed to yet answers with an empty list.

Auth: a project API key carrying registry:read.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
id*string

The registry's id.

Length1 <= length
repoId*string

The repository's id.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/projects/string/storage/container-registry/string/repositories/string/images"
{  "success": true,  "data": [    {      "digest": "string",      "tags": [        "string"      ],      "sizeBytes": -9007199254740991,      "pushedAt": "2019-08-24T14:15:22Z",      "lastPulledAt": "2019-08-24T14:15:22Z",      "scanStatus": "string",      "scanFindings": {        "property1": -9007199254740991,        "property2": -9007199254740991      }    }  ]}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
DELETE
/v1/projects/{projectId}/storage/container-registry/{id}/repositories/{repoId}/images/{digest}

Deletes one image and every tag pointing at it, to reclaim its storage. Use the digest from List images; deleting by tag is not supported, because a tag can move to another image between listing and deleting.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of registry:read and registry:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
id*string

The registry's id.

Length1 <= length
repoId*string

The repository's id.

Length1 <= length
digest*string

The image's digest, sha256: and 64 hex characters, URL-encoded (sha256%3A…).

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X DELETE "https://example.com/v1/projects/string/storage/container-registry/string/repositories/string/images/string"
Empty
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
DELETE
/v1/projects/{projectId}/storage/object-storage/{bucketId}

Deletes the bucket and every object in it, along with its CDN and the bucket-only access keys issued for it, and stops its billing. This cannot be undone.

The objects are deleted first. If the bucket's billing then cannot be stopped, the call answers 502 and the bucket stays listed, empty.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of object-storage:read and object-storage:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
bucketId*string

The bucket's id.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X DELETE "https://example.com/v1/projects/string/storage/object-storage/string"
{  "success": true,  "data": {}}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
PATCH
/v1/projects/{projectId}/storage/object-storage/{bucketId}/access

Sets whether anyone can read the bucket's objects from its public URL, and which folders public reads and the CDN serve. Send both fields; together they replace what the bucket had.

An empty publicPaths covers the whole bucket. Otherwise only objects under the listed folders are served, and everything else needs credentials, on the public URL and the CDN alike. A folder is letters, digits, ., _, - and /; a leading / is dropped and a trailing one added. Up to 10 folders.

The CDN does not need publicAccess: with it off and the CDN on, objects are served only through the CDN.

On a bucket still being created the settings are saved and applied once it is ready.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of object-storage:read and object-storage:write.

Rate limit: this endpoint is rate limited; a 429 means back off and retry.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
bucketId*string

The bucket's id.

Length1 <= length

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X PATCH "https://example.com/v1/projects/string/storage/object-storage/string/access" \  -H "Content-Type: application/json" \  -d '{    "publicAccess": true,    "publicPaths": [      "string"    ]  }'
{  "success": true,  "data": {    "publicAccess": true,    "publicPaths": [      "string"    ]  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
PATCH
/v1/projects/{projectId}/storage/object-storage/{bucketId}/cors

Replaces the bucket's CORS (cross-origin resource sharing) configuration, which decides which websites a browser lets read from and write to the bucket. The rules sent replace the whole configuration — send the complete list, not a change — and an empty corsRules clears it.

Each rule needs at least one entry in allowedOrigins (* allows any origin) and in allowedMethods. allowedHeaders, exposeHeaders and maxAgeSeconds are optional.

On a bucket still being created the rules are saved and applied once it is ready.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of object-storage:read and object-storage:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
bucketId*string

The bucket's id.

Length1 <= length

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X PATCH "https://example.com/v1/projects/string/storage/object-storage/string/cors" \  -H "Content-Type: application/json" \  -d '{    "corsRules": [      {        "allowedOrigins": [          "string"        ],        "allowedMethods": [          "GET"        ]      }    ]  }'
{  "success": true,  "data": {    "corsRules": [      {        "allowedOrigins": [          "string"        ],        "allowedMethods": [          "GET"        ],        "allowedHeaders": [          "string"        ],        "exposeHeaders": [          "string"        ],        "maxAgeSeconds": -9007199254740991      }    ]  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
PATCH
/v1/projects/{projectId}/storage/object-storage/{bucketId}/overage-limit

Sets the most overage, in whole USD, the bucket may run up in one billing period, and what happens when it gets there. Usage is checked once a night, so a limit is reached at the check after the overage passes it, not at the moment it does.

  • ALERT emails the project's billing contact, once per billing period. The bucket keeps working and its overage keeps being billed.
  • BLOCK emails the billing contact and refuses every read and write to the bucket — through access keys, public URLs and the CDN alike — until the next billing period starts. Listing and deleting objects still work, so data can be cleared out.

overageLimitUsd: null removes the limit. Removing it, raising it or switching to ALERT unblocks a blocked bucket at once; if its overage is still at the new limit, it is blocked again at the next nightly check.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of object-storage:read and object-storage:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
bucketId*string

The bucket's id.

Length1 <= length

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X PATCH "https://example.com/v1/projects/string/storage/object-storage/string/overage-limit" \  -H "Content-Type: application/json" \  -d '{    "overageLimitUsd": 0,    "overageLimitAction": "ALERT"  }'
{  "success": true,  "data": {    "overageLimitUsd": -9007199254740991,    "overageLimitAction": "ALERT",    "blocked": true  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
PATCH
/v1/projects/{projectId}/storage/object-storage/{bucketId}/tier

Moves the bucket to another tier. The change takes effect at once and is prorated to the billing cycle.

An upgrade charges the prorated difference to the project's payment method straight away; if the payment is declined the call answers 402 and the bucket stays on its tier. A downgrade charges nothing — the unused part of the current tier is credited to the account balance and applied to future invoices — and is refused with 400 while the bucket's current storage, egress or requests would not fit the smaller tier.

The bucket takes the new tier's allowance as it stands at the time of the change.

A bucket blocked at its overage limit is unblocked by the change. If its overage is still at the limit on the new tier, it is blocked again at the next nightly usage check.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of object-storage:read and object-storage:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
bucketId*string

The bucket's id.

Length1 <= length

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X PATCH "https://example.com/v1/projects/string/storage/object-storage/string/tier" \  -H "Content-Type: application/json" \  -d '{    "tier": "STARTER"  }'
{  "success": true,  "data": {    "tier": "STARTER"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
GET
/v1/projects/{projectId}/storage/object-storage/{bucketId}/usage

The bucket's storage, egress and requests, next to what it includes. Storage is what the bucket holds now; egress and requests count from the start of the current billing period. Usage above the allowance is billed as overage. The figures come from the storage metrics, which can lag by up to a day, and are cached for a few minutes.

A bucket still being created reports zero throughout, with allowance null.

Auth: a project API key carrying object-storage:read.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
bucketId*string

The bucket's id.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/projects/string/storage/object-storage/string/usage"
{  "success": true,  "data": {    "storageGb": 0,    "egressGb": 0,    "requests": -9007199254740991,    "tier": "STARTER",    "allowance": {      "storageGb": -9007199254740991,      "egressGb": -9007199254740991,      "requests": -9007199254740991    }  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}

Email templates

Stored, versioned emails that a send names instead of carrying its own subject and HTML. A template is written in the control panel, either as a React Email component in TSX or in the visual editor, and declares the variables a send supplies. Publishing freezes the draft into a numbered version; Send an email uses the published version unless it names another. [Email Templates](/docs/guides/email-templates) explains writing, previewing and publishing them. A template is addressed by its id or its alias wherever `{templateId}` appears. Aliases are lower-case letters, digits, `-` and `_`, unique in the project, and can be changed — the id never does. The calls here read templates, versions and folders, manage a template's name, alias, folder and sample data, roll back to an earlier version, and delete. Editing a template's content, publishing a version and sending a test are done in the control panel's editor, which builds what a publish stores. Templates themselves are free; the emails sent from them are billed like any other. A project can hold up to 200 templates and 100 folders, and each template keeps its 50 most recent versions.

IAM

AWS access keys for tooling that cannot use a Cosmoner API key — `aws ecr get-login-password`, Kubernetes image pull secrets, S3 clients. These endpoints issue scoped credentials for those cases and list every credential a project has out there. Cosmoner API keys are a different thing and are not listed here; see [API Keys](/docs/guides/api-keys). One credential can reach the project's container registries, its object storage buckets, or both. A grant either names individual repositories and buckets or covers every one in the project, **including those created later**. Each credential's `origin` says which shape it has: `project` is the current one, and `registry` and `bucket` are older single-service shapes that keep working with the scope they were created with. The secret access key is returned once, when the credential is issued, and never stored. Pulls made with these credentials go directly to AWS and are not metered as egress; storage is billed as usual. An API key issuing or re-scoping a credential needs the scopes for what the credential grants as well as `iam:write`, so a key cannot hand out access to buckets or repositories it could not reach itself.

On this page