Skip to main content

Crusoe Cloud integration

Crusoe Cloud is the wider Crusoe product your account already lives in. It holds three things this platform can reach:

  • Object storage — buckets that hold files, and that speak the same API as object storage-compatible, so any object storage client works against them.
  • A private container registry, the Crusoe Container Registry or CCR. A registry is where built container images are stored so a machine can pull and run them.
  • Managed Inference model deployments — the language models your account can serve.

This page shows you how to connect one platform project to one Crusoe Cloud project, and what you get once you have.

A project must be mapped before it can deploy anything

Everything the platform builds for you — agents, functions, MCP servers, and source-deployed services — is stored as a container image in a repository in your own CCR, under the credential mapped here. Your account, your quota, your bill, your retention policy, and no other customer's images beside yours.

So the mapping is not optional any more. A project with no credential is refused at submit, before anything is built:

this project cannot deploy yet: its container images are built into your own Crusoe Cloud
container registry, and no Crusoe Cloud credential is mapped to this project.

Map it once and you are done — a project that is already mapped needs no changes and behaves exactly as before. The quickstarts now open with this step: connect your Crusoe Cloud account.

You do not pre-create a repository. The platform creates one per workload on its first build, named cai-<project-short>-<workload>.

Once a project is mapped, five jobs stop requiring a trip to the Crusoe Cloud UI. You can deploy (the platform pushes your built images into your own registry), create and list buckets, create and list CCR repositories, read the models your account can serve, and mint the project's Managed Inference key from the credential you already supplied — an agent that calls a model is refused at submit until the project has one. All of it works from the platform console or API.

What a mapping is

A mapping is two things stored together: a Crusoe Cloud access key, and the id of the one Crusoe Cloud project that key should act on. The platform keeps the pair in your project's secret store and uses it to call Crusoe Cloud on your behalf.

Three things follow from that design, and they are worth knowing before you start:

  • One to one. A platform project maps to exactly one Crusoe Cloud project. There is no field for a second.
  • Write-only. The platform checks the secret key once, stores it, and never gives it back. Not from the mapping read, not from the secrets API, and not to a platform admin.
  • The mapping is the front door. Deploys, buckets, repositories, the model list, private-image pulls, the S3 credential your bucket triggers use, and the project's Managed Inference key all come from the same stored credential. Nothing else works until it is set.

Before you begin

  • A Crusoe Cloud access key ID and secret key. Create these in Crusoe Cloud, not here — the platform never mints them.
  • The project resource-admin role to set or clear a mapping, or to create buckets and repositories. Any project member can read the mapping and the three discovery lists.
  • For the API examples, the endpoint (https://api.codyhill.dev) and a token. See API authentication for how to get a token. Everything on this page is in the console too, and the console needs none of this setup.
export CAI_API=https://api.codyhill.dev
export CAI_TOKEN=cai_xxxx_yyyy # from platformctl login, or an API key
export CAI_PROJECT=your-project-uuid # platformctl projects list

Map your project

In the console

  1. Open your project and go to Project Settings.
  2. Click Map to Crusoe Cloud.
  3. Paste the Access key ID and Secret key. Leave Crusoe Cloud project blank — the field is hidden until the platform finds it needs one.
  4. Click Map project.

The dialog says plainly what happens to what you typed:

The secret key is validated against Crusoe Cloud and stored in the project secret store. It is never returned by any read, so this dialog is the only place it is ever handled.

With the API

PUT /v1/projects/{projectID}/crusoe-cloud — project resource-admin.

FieldTypeRequiredNotes
access_key_idstringYesThe key's public identifier.
secret_keystringYesValidated against Crusoe Cloud, then stored. Never echoed back.
cc_project_idstringNoWhich Crusoe Cloud project to map to. Required only when the key can reach more than one.
regionstringNoPin a region. Left blank, the platform discovers one (see below).
curl -s -X PUT "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud" \
-H "Authorization: Bearer $CAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"access_key_id": "CRUSOEEXAMPLEKEYID", "secret_key": "your-secret-key"}'

You should see:

{"mapped": true,
"cc_project": {"id": "b6f1a0c2-1f2e-4a55-9a4e-2c0a7f8d3e11", "name": "ml-platform"},
"region": "us-east1-a"}

The region key is present only when a region is known.

What the platform does before it stores anything

The order matters, because it decides which error you get:

  1. Blank fields are rejected locally, before any network call: 400access_key_id and secret_key are required.
  2. The key is tested live, by listing the Crusoe Cloud projects it can see. If Crusoe Cloud refuses the key, you get 400, and the message starts the Crusoe Cloud credential was rejected:. If the key could not be tested at all because Crusoe Cloud was unreachable, you get 502could not reach Crusoe Cloud to validate the credential:. That split is deliberate. A 502 does not mean your key is bad.
  3. The Crusoe Cloud project is resolved from that list (see the next section).
  4. A region is discovered if you did not pin one. The platform looks at the first model or bucket in the account that reports a region. This is a best effort. If nothing reports one, the mapping still succeeds, just without a region.
  5. The credential is stored, and two Secrets are derived from it in your project: ccr-pull, for private image pulls, and crusoe-object-store, the S3 key an object-store trigger names. Both are best effort — the credential is already saved, so a failure here does not fail the PUT, and both self-heal on the next save. crusoe-object-store is minted only when the project holds none, and a half-written one (either of its two keys empty) counts as none.

When your key can reach several projects

This is the most common first-attempt failure, so it gets its own section.

If you omit cc_project_id and the key can see exactly one Crusoe Cloud project, that project is chosen for you. Otherwise:

Projects the key can seeResult
0400this credential can access no Crusoe Cloud projects, so there is nothing to map to
1Mapped automatically, no cc_project_id needed
2 or more400this credential can access N Crusoe Cloud projects; choose one with cc_project_id

Naming a project the key cannot reach is also refused:

cc_project_id "..." is not among the 3 project(s) this credential can access

Where to find a cc_project_id. The console handles this for you. When the map form hits the multi-project error, it reveals the Crusoe Cloud project field. If the error named the projects, it also adds a picker labeled Pick a Crusoe Cloud project. Your secret key stays in the form, so you choose and submit again without retyping it.

Calling the API directly, you do not have to leave to find the id either — the 400 carries the candidates. Its body is the ordinary error envelope plus cc_projects, sorted by name:

{"error": "this credential can access 3 Crusoe Cloud projects; choose one with cc_project_id",
"request_id": "req_01J…",
"cc_projects": [{"id": "b6f1a0c2-1f2e-4a55-9a4e-2c0a7f8d3e11", "name": "ml-platform"},
{"id": "0c3d7e91-88a1-4d0c-b2f5-6a1e9c40b772", "name": "research"}]}

Pick one and send the PUT again with cc_project_id. Pinning an id the key cannot reach returns the same list, for the same reason. Above 500 projects the list is cut short, cc_projects_truncated is true, and the message says so rather than letting you scan a chooser that is quietly missing what you wanted.

Read the mapping

GET /v1/projects/{projectID}/crusoe-cloud — any project member.

curl -s "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud" \
-H "Authorization: Bearer $CAI_TOKEN"

Unmapped:

{"mapped": false}

Mapped: the same shape the PUT returned. Notice what is missing. There is no access_key_id and no secret_key. No call on this API ever returns the secret key.

The stored credential lives in the project secret store under the reserved name crusoe-cloud-credential. That name is invisible to the ordinary secrets API. Create, get, reveal, and delete against it all return 404 and point you back here. See the Secrets API reference.

Check whether the credential still works

A key that worked when you mapped it can be revoked in Crusoe Cloud afterwards, and the first you would otherwise hear of it is a failing deploy. A background sweep records a verdict, and two routes expose it — both any project member, because reading whether your project's credential still works is exactly what you need when a deploy is failing.

RouteWhat it does
GET /v1/projects/{projectID}/crusoe-cloud/credential-checkReturn the recorded verdict. No call to Crusoe Cloud.
POST /v1/projects/{projectID}/crusoe-cloud/credential-checkRe-check now, against Crusoe Cloud.
{"state": "valid", "blocking": false, "checking": true, "detail": "",
"checked_at": "2026-07-02T14:11:09Z", "valid_at": "2026-07-02T14:11:09Z",
"next_check_at": "2026-07-03T02:11:09Z"}

state is one of unknown (never checked, or not mapped), valid, invalid, or unreachable. Only invalid sets blocking, and that distinction is the point of having four: unreachable means Crusoe Cloud could not be asked — a timeout, a 5xx, a rate limit — which is not a verdict about your key and must not be read as one. checking reports whether a checker is running at all; false means the answer is not being maintained.

In the console this is the Check again button on the credential banner. Two things about the re-check are worth knowing:

  • It is floored at 30 seconds. A re-check inside that window returns the stored answer rather than spending another upstream call — holding down the button cannot generate load against your own account.
  • A valid verdict re-derives what the credential produces. It is the one-click way to give a project its crusoe-object-store Secret if it was mapped before that Secret existed, or if the Secret was deleted. Only a valid verdict does this, because writing S3 credentials from a key Crusoe Cloud has just refused would replace a working Secret with a broken one.

Create and list object-storage buckets

object storage-compatible buckets in the mapped Crusoe Cloud project. In the console they are the Object storage buckets section of the Crusoe Cloud page.

List buckets

GET /v1/projects/{projectID}/crusoe-cloud/buckets — any project member.

curl -s "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud/buckets" \
-H "Authorization: Bearer $CAI_TOKEN"

You should see:

{"buckets": [{"name": "incoming-docs",
"s3_endpoint": "https://object.us-east1-a.crusoecloudcompute.com",
"region": "us-east1-a",
"created_at": "2026-07-02T14:11:09Z"}],
"next_page_token": ""}
next_page_token is always empty here

The three Crusoe Cloud lists carry a next_page_token field so their shape matches every other list on the platform. Here it is always "", because Crusoe Cloud returns the whole set in one call. Do not write a paging loop against it. A client that keeps requesting until the token changes will loop forever.

Create a bucket

POST /v1/projects/{projectID}/crusoe-cloud/buckets — project resource-admin. Both name and region are required.

curl -s -X POST "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud/buckets" \
-H "Authorization: Bearer $CAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "incoming-docs", "region": "us-east1-a"}'

You should see (HTTP 201):

{"name": "incoming-docs",
"s3_endpoint": "https://object.us-east1-a.crusoecloudcompute.com",
"region": "us-east1-a",
"created_at": "2026-07-02T14:11:09Z"}
A bucket must name a region; a repository need not

There is no server-side default for a bucket's region. Leave it out and the platform API refuses before it ever calls Crusoe Cloud:

region is required to create a bucket

A repository is the other way round: send no region and Crusoe Cloud picks one. One create dialog in the console covers both, and it tracks that difference — the Region field is marked required for a bucket and pre-filled with your mapping's region when one is known, and stays optional for a repository ("Optional. Blank lets Crusoe Cloud choose the repository's region.").

A region is a Crusoe Cloud location string that looks like us-east1-a. You can copy a valid one from three places: your mapping, an existing bucket, or the middle of a CCR host name such as registry.us-east1-a.ccr.crusoecloudcompute.com. No endpoint lists the valid regions. Pick carefully: a region fixes where the bytes physically live and cannot be changed afterwards, which is why the console has no hardcoded fallback to offer you.

A blank name is refused with name is required.

Create and list container-registry repositories

A CCR repository is where you push a private container image. That is the first half of deploying your own image to the platform. The second half is the ccr-pull Secret, described below.

List repositories

GET /v1/projects/{projectID}/crusoe-cloud/repositories — any project member.

{"repositories": [{"name": "checkout-api",
"registry_url": "registry.us-east1-a.ccr.crusoecloudcompute.com/checkout-api.7dhg29ls",
"region": "us-east1-a",
"id": "9d2f...c1"}],
"next_page_token": ""}

registry_url is the address you push your image to — see push an image to your repository below. It is also the value you hand the platform when you deploy that image as a serverless service.

Create a repository

POST /v1/projects/{projectID}/crusoe-cloud/repositories — project resource-admin. Only name is required; region is optional here (unlike buckets).

curl -s -X POST "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud/repositories" \
-H "Authorization: Bearer $CAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "checkout-api"}'

You should see (HTTP 201):

{"name": "checkout-api",
"registry_url": "registry.us-east1-a.ccr.crusoecloudcompute.com/checkout-api.7dhg29ls",
"region": "us-east1-a",
"id": "9d2f...c1"}

Push an image to your repository

Creating a repository gives you an address. Getting an image into that address is a separate job, and you do it against Crusoe Cloud directly with ordinary registry commands. The platform has no push route, and it mints no push credential for you. The examples use docker; any tool that speaks the registry protocol works the same way.

You need three things:

  • A container image built on your own machine.
  • The repository's registry_url, from the list or create call above.
  • A CCR registry token. Create one in Crusoe Cloud — the platform has no route that issues you one. Your Crusoe Cloud access key ID and secret key will not work here, because CCR does not accept them as a registry password. That is the same reason the platform mints a separate token for its own pulls, described in the next section.

1. Sign in to the registry. The registry host is the part of registry_url before the first slash. The username is the email address on your Crusoe Cloud account, and the password is the CCR token:

export CCR_HOST=registry.us-east1-a.ccr.crusoecloudcompute.com
docker login --username you@example.com --password-stdin "$CCR_HOST" < ./ccr-token.txt

You should see:

Login Succeeded

2. Tag the image with the full repository address, then push it. The address is the whole registry_url, plus a tag you choose:

export CCR_REPO=registry.us-east1-a.ccr.crusoecloudcompute.com/checkout-api.7dhg29ls
docker tag checkout-api:latest "$CCR_REPO:v1"
docker push "$CCR_REPO:v1"

3. Deploy that exact reference. $CCR_REPO:v1 — the whole string, tag included — is what you paste into the Serverless deploy dialog's Container image field, or send as image on the API. If the project is mapped, that dialog offers a Pick a Crusoe Cloud repository dropdown which fills in the address for you; you still append the tag yourself. See deploy a service.

A successful push does not mean the platform can pull

Pushing proves your credentials work. Pulling uses a different credential — the ccr-pull Secret below — and that one is frozen at the moment you last saved the mapping. A repository you created after mapping is not in it. Save the mapping again before you deploy from a brand-new repository.

Delete a bucket or a repository

Both deletes destroy data in your own Crusoe Cloud account, and neither is undoable from here. Both are project resource-admin, like the creates they undo. Note the addressing asymmetry, which is the upstream API's and not a typo: a bucket is addressed by name, a repository by id.

curl -s -X DELETE "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud/buckets/incoming-docs" \
-H "Authorization: Bearer $CAI_TOKEN"

curl -s -X DELETE "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud/repositories/9d2f...c1" \
-H "Authorization: Bearer $CAI_TOKEN"

Each answers with what it removed and a note saying plainly what that cost:

{"deleted": "incoming-docs",
"note": "The bucket and its contents are gone from your Crusoe Cloud account. Workloads that addressed it will now fail to reach it."}
{"deleted": "9d2f...c1",
"note": "The repository and its images are gone from your Crusoe Container Registry. Any workload whose image lived there can no longer be pulled - including on a restart of one that is running now."}

That last clause is the one that catches people. Deleting a repository takes its images with it, and a service running from one of those images keeps serving until its next pull — a scale-up from zero, a restart, or a redeploy — and then cannot start. A blank name or id is refused with a bucket name is required or a repository id is required. There is no platformctl command for either delete; use the API or the console.

There is also GET /v1/projects/{projectID}/crusoe-cloud/buckets/{bucket} — any project member — which returns one bucket, including the endpoint the list route has to look up per bucket.

The ccr-pull Secret, and the trap in it

To pull a private CCR image, the platform needs registry credentials sitting in your project. Your Crusoe Cloud access key will not do, because CCR does not accept one as a Docker login. So when you save a mapping, the platform:

  1. Lists your CCR repositories.
  2. Works out the distinct registry hosts those repositories live on.
  3. Mints a short-lived CCR registry token scoped to exactly those hosts.
  4. Writes it into an image-pull credential named ccr-pull in your project.

Deploys attach that credential automatically, and only for images whose host is on that list.

An image you pushed yourself, to a host no platform build has touched

Every deploy the platform builds mints one CCR token and uses it in both directions: to push the image it just built, and to refresh ccr-pull for that registry host. The refresh is per host and leaves other hosts' entries alone, and the deploy fails outright if it cannot be written — so a project that deploys agents, functions, MCP servers or source-built services keeps its pull credential current without you doing anything.

The gap is the image you pushed by hand, to a host no platform build has pushed to. Its entry is whatever the last mapping save left behind, and that entry expires. The host list was captured when you saved the mapping, so a repository created afterwards is not in it at all — which bites hardest when it is in a region where you had none before, because that is a new host. Your push succeeds, the deploy then fails to pull, and nothing on screen points at the cause.

The fix is to save the mapping again with the same credential. Re-running the PUT re-lists your repositories and re-mints the token against the current host list. Do it before deploying a hand-pushed image from a brand-new repository, and again if one that used to pull starts failing.

One more fact about this Secret that only shows up as a symptom:

  • Zero repositories means no Secret at all. If your Crusoe Cloud project has no CCR repositories at the moment you map, the platform creates nothing, and it removes any stale ccr-pull Secret. That is not a problem for a platform build — it creates its repository and writes the Secret on the spot — but a hand-pushed image has nothing to pull with until you save the mapping again.

Mint the project's inference key

An agent that calls a model needs a Managed Inference key, and that key is issuable from the Crusoe Cloud credential you just mapped. There is no second key to go and find.

POST /v1/projects/{projectID}/crusoe-cloud/inference-key — project resource-admin. In the console it is Project Settings → Managed Inference → Mint a key from Crusoe Cloud.

curl -s -X POST "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud/inference-key" \
-H "Authorization: Bearer $CAI_TOKEN"

A body is optional. Send {"alias": "...", "expires_at": "..."} to name the key as it will appear in your Crusoe Cloud key list or to give it an expiry; left off, the alias is crusoe-ai-platform-<project-short> and the key does not expire.

You should see (HTTP 201):

{"mapped": true, "key_suffix": "9f2c", "key_id": "3b1d...e7", "alias": "crusoe-ai-platform-acme",
"expires_at": "...", "rotated": false,
"note": "Minted from this project's Crusoe Cloud credential and scoped to Crusoe Cloud project ..."}

The key itself is never returned — key_suffix is its last four characters, which is what the console shows, and expires_at is the expiry Crusoe Cloud recorded for it. The minted key is scoped to the mapped Crusoe Cloud project, so inference spend lands beside that project's buckets and repositories. Re-minting is a rotation: rotated is true, your chosen default model is kept, and the previous key stays valid in Crusoe Cloud until you revoke it there.

Until a project has one, a deploy of a model-backed agent is refused at submit, before anything is built:

this project cannot deploy a model-backed agent yet: agents call inference with YOUR OWN key from
Crusoe Intelligence Foundry, and no key is mapped to this project.

Functions, containers and MCP servers are unaffected — they do not call the model. Agents already running keep the key and model baked into their current revision until they are redeployed.

Find the models your account can serve

GET /v1/projects/{projectID}/crusoe-cloud/models — any project member. Read-only: there is no create, no update, no delete. In the console it is the Dedicated deployments and Model catalogue sections of Project Settings.

The response has two independent halves, and either can fail on its own:

{"models": [{"id": "dep-4f1c...",
"name": "glm-prod",
"model_name": "zai/GLM-5.2",
"region": "us-east1-a",
"status": "running",
"context_length": 131072}],
"models_error": "",
"catalog": [{"model_name": "zai/GLM-5.2", "provider": "zai"}],
"catalog_error": "",
"next_page_token": ""}

models is deployments — dedicated Managed Inference endpoints this Crusoe Cloud project has provisioned and pays for by the hour.

FieldWhat it is
idThe deployment's id in Crusoe Cloud.
nameThe deployment's display name.
model_nameThe string a request actually names — this is what you put in CHAT_MODEL.
regionWhere the deployment runs.
statusWhether it is serving. The console badges anything matching ready, active, available, running, or serving as healthy.
context_lengthHow many tokens the model can hold in one conversation.

catalog is availability — what Managed Inference will serve this project on request, one entry per model, carrying model_name and the vendor prefix as provider. This is usually the half that answers "which model should I use?": a project that has never provisioned a dedicated endpoint has zero models and a full catalog, which is the normal state, not an empty account. Set CHAT_MODEL to a model_name from either half. See secrets and environment variables and foundry_model().

The catalogue is read from the project's own inference key first — the same key an agent presents at inference time — and falls back to the Crusoe Cloud self-serve catalogue only when there is no key yet or that call fails. That order is why a project with an inference key can see models the mapped access key alone cannot list: measured against a live project, the mapped access key was rejected with 401 while listing buckets perfectly well, and the project's inference key listed 15 models.

An error string is not an empty list

models_error and catalog_error mean that half could not be fetched, and the list beside them is empty because nothing was read — not because your account has nothing. Read them before you report emptiness to anyone. This is not hypothetical: one upstream 401 on the deployments call once became the sentence "the mapped Crusoe Cloud project reports no Managed Inference models" in the console. Only when both halves fail does the route return an error instead of a body.

Two honest caveats:

  • Mapping does not repoint your agents, but it can supply their key. The platform injects the inference endpoint your agents call, and a mapping does not change it. The credential is a different matter: mint the project's key from the same Crusoe Cloud credential (above), and every agent without its own token inherits it as MODEL_API_KEY. PATCH /v1/projects/{projectID}/inference, or Change default model in the console, sets the project's default model without re-supplying the key. A per-agent MODEL_API_KEY secret still wins over the project's — set one only when a single agent must use a different key or bill separately, and note that a blank or placeholder value is ignored rather than inherited, so clearing it falls back to the project key.
  • The list can be briefly stale. See caching, below.

Note that platformctl crusoe-cloud models list prints only the models half — deployments — and says no models deployed in the connected Crusoe Cloud project when there are none. The catalogue is console and API only today.

Use a bucket with an object-store trigger

The trigger form on the Serverless page has a bucket picker, and it reads the same list you just saw. Pick a bucket and it fills in the bucket name, the object storage endpoint, the region, and the credentials Secret.

Picking a bucket writes its region over whatever is in the box — including the us-east-1 the field ships pre-filled with. The bucket is the authority on where it lives, and a region that disagrees with it is not a preference worth preserving: the trigger is created, looks configured, and its poller fails at connect time with nothing on screen explaining why. The endpoint is written only when Crusoe Cloud reported one, and the credentials Secret only when that field is empty, so neither can blank something you typed.

Endpoints look like https://object.us-southcentral1-a.crusoecloudcompute.com — the full availability zone, path-style addressing, reachable from inside your Crusoe network. Crusoe Cloud only reports this on its per-bucket route, not on the list, so the platform looks it up for you; on the rare bucket where that lookup does not come back, type it in yourself.

The Secret is crusoe-object-store, and your Crusoe Cloud access key is not an S3 key

credentials_secret_name names a Secret stored in your own project — not a platform secret from the Secrets Manager. You do not build it. Mapping the project mints a real S3 key from your Crusoe Cloud connection and writes it there as crusoe-object-store, holding the two field names the object-store trigger reads: aws.accessKey and aws.secretKey. That name is what goes in the field, and both console forms put it there for you — Add trigger fills it when the box is empty, and the Functions deploy form offers crusoe-object-store (from your Crusoe Cloud connection) and preselects it.

Do not paste the Crusoe Cloud access key you mapped. It is not an S3 key, and object storage answers it with 403 The AWS access key Id you provided does not exist in our records — the same refusal CCR gives it as a registry password. A hand-built Secret holding it produces a trigger that is created, reports no error, and never delivers, because a bucket trigger only authenticates when its poller connects. The region has the same shape of failure: it must match the bucket's.

A project mapped before this Secret existed has none. Open Project Settings and press Check again — a check that comes back valid re-derives it, and only a valid one does, because writing S3 credentials from a key Crusoe Cloud has just refused would replace a working Secret with a broken one. Unmapping deletes it.

Leave the field empty and the API refuses up front, before anything is created:

source.objectstore.credentials_secret_name is required, and the secret must belong to this project

See triggers for the rest of the object-store trigger contract.

Unmap a project

DELETE /v1/projects/{projectID}/crusoe-cloud — project resource-admin. In the console it is the Unmap action on the Crusoe Cloud page.

curl -s -X DELETE "$CAI_API/v1/projects/$CAI_PROJECT/crusoe-cloud" \
-H "Authorization: Bearer $CAI_TOKEN"

You should see:

{"mapped": false, "unmapped": true}

Unmapping a project that was never mapped is 404this project is not mapped to Crusoe Cloud.

What unmapping does and does not do:

Unmapping doesUnmapping does not
Delete the stored access keyDelete any bucket in Crusoe Cloud
Delete the project's ccr-pull SecretDelete any CCR repository or image
Delete the project's crusoe-object-store SecretDelete the project's inference key, or stop agents calling the model
Stop the project deploying anything new — every build needs somewhere to pushStop what is already running, until its next image pull
Make the console's bucket and repository pickers fall back to manual entryTouch anything else in your Crusoe Cloud account
Unmap stops deploys immediately, and breaks the next pull of a private image

Deploys stop at once. Agents, functions, MCP servers, and source-deployed services are all built into your own registry, so with no credential there is nowhere to push. The next deploy is refused with this project cannot deploy yet: .... Connect a key again and it works as before.

The next pull of a private image also breaks

The console's confirmation dialog correctly says your buckets and repositories survive. It does not mention the one thing that breaks. Deleting ccr-pull takes away registry access, so any running service built from a CCR image fails on its next image pull. That next pull comes on a scale-up from zero, a restart, or a redeploy. Instances already running keep serving until then, which is what makes this easy to miss. Map a key again to restore access.

Object-store triggers stop authenticating too

crusoe-object-store goes with the mapping, deliberately: a key the customer has disconnected must not keep authenticating their bucket triggers. Every trigger naming that Secret fails on its next poll — by default within 60 seconds — and the bucket itself is untouched, so nothing about the bucket explains it.

Mapping a key again re-mints the Secret. On a project that is still mapped and has merely lost it, Project Settings → Check again re-derives it, but only when that check reports the credential valid.

Discovery lists are cached for 45 seconds

Buckets, repositories, and models are cached per project for 45 seconds. Without that cache, a console page with three dropdowns would call Crusoe Cloud three times on every render. The trade-off: a bucket created outside the platform can take up to 45 seconds to appear here.

Anything you create or delete through this API clears the cache immediately, and so does saving or clearing a mapping. You never wait to see your own change.

Errors you can hit

StatusMessage (verbatim)What to do
400access_key_id and secret_key are requiredFill in both fields.
400the Crusoe Cloud credential was rejected: ...The key is wrong, revoked, or mistyped.
400this credential can access N Crusoe Cloud projects; choose one with cc_project_idAdd cc_project_id.
400region is required to create a bucketAdd a region such as us-east1-a.
400name is requiredName the bucket or repository.
400a bucket name is requiredOn a bucket GET or DELETE. A bucket is addressed by name.
400a repository id is requiredOn a repository DELETE. A repository is addressed by id, not name.
400the stored Crusoe Cloud credential was rejected by the API: ...The stored key stopped working. Save the mapping again with a valid key.
404this project is not mapped to Crusoe CloudOnly on DELETE. Nothing to unmap.
409this project is not mapped to Crusoe Cloud. Set a credential with PUT /v1/projects/{id}/crusoe-cloud first.Map before using a discovery route.
409this project cannot deploy yet: its container images are built into your own Crusoe Cloud container registry, and no Crusoe Cloud credential is mapped to this project. ...On a deploy, not a route here. Map the project, then deploy again. Nothing was built.
409the stored Crusoe Cloud mapping credential is invalid or unparseable; re-map the project with PUT /v1/projects/{id}/crusoe-cloud.Save the mapping again.
502could not reach Crusoe Cloud to validate the credential: ...Crusoe Cloud is unreachable. Your key is not implicated — retry.
502the Crusoe Cloud API call failed: ...An upstream failure, not your request. Retry.
503(the secret store's own message — it is sealed, unreachable, or not configured)Nothing was stored or read. Ask your administrator; retry once the store is available.

The 409 cases matter because they do not mean "the resource is missing". They mean the mapping itself is absent or unusable, which calls for a different fix than a bad request does.

What lands in the audit log

Every write here is recorded in your project's audit log:

ActionWhen
project.crusoe-cloud.mapA mapping is saved
project.crusoe-cloud.unmapA mapping is removed
project.crusoe-cloud.bucket.createA bucket is created
project.crusoe-cloud.bucket.deleteA bucket is deleted
project.crusoe-cloud.repository.createA repository is created
project.crusoe-cloud.repository.deleteA repository is deleted
project.crusoe-cloud.credential-checkThe credential is re-checked (the POST, not the GET)
project.inference.mintAn inference key is minted or re-minted

Reads are not audited. That covers the mapping status, the three discovery lists, and reading the recorded credential verdict; none of them ever returns the secret key. Asking for a fresh check is a write against your Crusoe Cloud account, so it is recorded.

Endpoints at a glance

MethodPathWho
GET/v1/projects/{projectID}/crusoe-cloudProject member
PUT/v1/projects/{projectID}/crusoe-cloudProject resource-admin
DELETE/v1/projects/{projectID}/crusoe-cloudProject resource-admin
GET/v1/projects/{projectID}/crusoe-cloud/bucketsProject member
POST/v1/projects/{projectID}/crusoe-cloud/bucketsProject resource-admin
GET/v1/projects/{projectID}/crusoe-cloud/buckets/{bucket}Project member
DELETE/v1/projects/{projectID}/crusoe-cloud/buckets/{bucket}Project resource-admin
GET/v1/projects/{projectID}/crusoe-cloud/repositoriesProject member
POST/v1/projects/{projectID}/crusoe-cloud/repositoriesProject resource-admin
DELETE/v1/projects/{projectID}/crusoe-cloud/repositories/{repository}Project resource-admin
POST/v1/projects/{projectID}/crusoe-cloud/inference-keyProject resource-admin
GET/v1/projects/{projectID}/crusoe-cloud/modelsProject member
GET/v1/projects/{projectID}/crusoe-cloud/credential-checkProject member
POST/v1/projects/{projectID}/crusoe-cloud/credential-checkProject member

The core platform API at $CAI_API serves all of these. They all take the standard Authorization: Bearer header and return the standard error envelope, {"error": "...", "request_id": "..."}. A project you have no access to returns 404, never 403 — see security overview.

Do all of this from the CLI

Connecting, the three discovery lists, the two creates and disconnecting all have a platformctl command, which is what you want in a script or a CI job:

CommandWhat it does
platformctl crusoe-cloud connectStore the project's Crusoe Cloud credential. Project admin only.
platformctl crusoe-cloud showReport whether the project is connected, and to what.
platformctl crusoe-cloud buckets listList the connected project's object-storage buckets.
platformctl crusoe-cloud buckets create <name> --region <region>Create a bucket. --region is required. Project admin only.
platformctl crusoe-cloud repositories listList container-registry repositories.
platformctl crusoe-cloud repositories create <name> [--region <region>]Create a repository. Without --region, Crusoe Cloud chooses one. Project admin only.
platformctl crusoe-cloud models listList the dedicated Managed Inference deployments in the connected project — the models half only, not the catalogue.
platformctl crusoe-cloud disconnectRemove the project's credential. The project can no longer deploy until you connect one again. Project admin only.

The rest of the page is console and API only: deleting a bucket or a repository, minting the inference key, running a credential check, and reading the model catalogue have no CLI command today.

Connecting reads the secret key from a file or standard input — never from a flag:

platformctl crusoe-cloud connect \
--access-key-id 'CRUSOEEXAMPLEKEYID' \
--secret-key-file ./crusoe-secret-key.txt

You should see:

connected - the credential was accepted by Crusoe Cloud and stored.
crusoe cloud project: ml-platform
project id: b6f1a0c2-1f2e-4a55-9a4e-2c0a7f8d3e11
region: us-east1-a

A project with no credential is not an error here. platformctl crusoe-cloud show says so plainly and names the command that fixes it:

not connected: this project has no Crusoe Cloud credential.
connect one with 'platformctl crusoe-cloud connect --access-key-id <id>'.

If the account has nothing deployed yet, the region line reads not known yet (pass --region when creating a bucket) instead. That is not a failure — the platform only infers a region from a deployed model or an existing bucket.

There is no --secret-key flag, and that is deliberate

A secret passed as a command-line flag is visible to every other process on the machine and gets written into your shell history and your CI logs. platformctl crusoe-cloud connect therefore accepts the secret key only from a file (--secret-key-file) or standard input, which is the default. Pipe it if you prefer: echo "$SECRET" | platformctl crusoe-cloud connect --access-key-id 'AKIA...'.

The access key id is fine as a flag — it is an identifier, not a secret.

Two more flags on connect matter in specific cases:

  • --cc-project-id — required only when the key can reach more than one Crusoe Cloud project. See when your key can reach several projects.
  • --region — pins the connection's region. Left off, the platform infers it from what the account already has deployed.

Next steps