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.
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
- Open your project and go to Project Settings.
- Click Map to Crusoe Cloud.
- Paste the Access key ID and Secret key. Leave Crusoe Cloud project blank — the field is hidden until the platform finds it needs one.
- 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.
| Field | Type | Required | Notes |
|---|---|---|---|
access_key_id | string | Yes | The key's public identifier. |
secret_key | string | Yes | Validated against Crusoe Cloud, then stored. Never echoed back. |
cc_project_id | string | No | Which Crusoe Cloud project to map to. Required only when the key can reach more than one. |
region | string | No | Pin 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:
- Blank fields are rejected locally, before any network call:
400—access_key_id and secret_key are required. - 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 startsthe Crusoe Cloud credential was rejected:. If the key could not be tested at all because Crusoe Cloud was unreachable, you get502—could not reach Crusoe Cloud to validate the credential:. That split is deliberate. A502does not mean your key is bad. - The Crusoe Cloud project is resolved from that list (see the next section).
- 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.
- The credential is stored, and two Secrets are derived from it in your project:
ccr-pull, for private image pulls, andcrusoe-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 thePUT, and both self-heal on the next save.crusoe-object-storeis 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 see | Result |
|---|---|
| 0 | 400 — this credential can access no Crusoe Cloud projects, so there is nothing to map to |
| 1 | Mapped automatically, no cc_project_id needed |
| 2 or more | 400 — this 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.
| Route | What it does |
|---|---|
GET /v1/projects/{projectID}/crusoe-cloud/credential-check | Return the recorded verdict. No call to Crusoe Cloud. |
POST /v1/projects/{projectID}/crusoe-cloud/credential-check | Re-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-storeSecret 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": ""}
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"}
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.
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:
- Lists your CCR repositories.
- Works out the distinct registry hosts those repositories live on.
- Mints a short-lived CCR registry token scoped to exactly those hosts.
- Writes it into an image-pull credential named
ccr-pullin your project.
Deploys attach that credential automatically, and only for images whose host is on that list.
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-pullSecret. 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.
| Field | What it is |
|---|---|
id | The deployment's id in Crusoe Cloud. |
name | The deployment's display name. |
model_name | The string a request actually names — this is what you put in CHAT_MODEL. |
region | Where the deployment runs. |
status | Whether it is serving. The console badges anything matching ready, active, available, running, or serving as healthy. |
context_length | How 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.
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-agentMODEL_API_KEYsecret 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.
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 404 — this project is not mapped to Crusoe Cloud.
What unmapping does and does not do:
| Unmapping does | Unmapping does not |
|---|---|
| Delete the stored access key | Delete any bucket in Crusoe Cloud |
Delete the project's ccr-pull Secret | Delete any CCR repository or image |
Delete the project's crusoe-object-store Secret | Delete the project's inference key, or stop agents calling the model |
| Stop the project deploying anything new — every build needs somewhere to push | Stop what is already running, until its next image pull |
| Make the console's bucket and repository pickers fall back to manual entry | Touch anything else in your Crusoe Cloud account |
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 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.
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
| Status | Message (verbatim) | What to do |
|---|---|---|
| 400 | access_key_id and secret_key are required | Fill in both fields. |
| 400 | the Crusoe Cloud credential was rejected: ... | The key is wrong, revoked, or mistyped. |
| 400 | this credential can access N Crusoe Cloud projects; choose one with cc_project_id | Add cc_project_id. |
| 400 | region is required to create a bucket | Add a region such as us-east1-a. |
| 400 | name is required | Name the bucket or repository. |
| 400 | a bucket name is required | On a bucket GET or DELETE. A bucket is addressed by name. |
| 400 | a repository id is required | On a repository DELETE. A repository is addressed by id, not name. |
| 400 | the stored Crusoe Cloud credential was rejected by the API: ... | The stored key stopped working. Save the mapping again with a valid key. |
| 404 | this project is not mapped to Crusoe Cloud | Only on DELETE. Nothing to unmap. |
| 409 | this project is not mapped to Crusoe Cloud. Set a credential with PUT /v1/projects/{id}/crusoe-cloud first. | Map before using a discovery route. |
| 409 | 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. ... | On a deploy, not a route here. Map the project, then deploy again. Nothing was built. |
| 409 | the stored Crusoe Cloud mapping credential is invalid or unparseable; re-map the project with PUT /v1/projects/{id}/crusoe-cloud. | Save the mapping again. |
| 502 | could not reach Crusoe Cloud to validate the credential: ... | Crusoe Cloud is unreachable. Your key is not implicated — retry. |
| 502 | the 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:
| Action | When |
|---|---|
project.crusoe-cloud.map | A mapping is saved |
project.crusoe-cloud.unmap | A mapping is removed |
project.crusoe-cloud.bucket.create | A bucket is created |
project.crusoe-cloud.bucket.delete | A bucket is deleted |
project.crusoe-cloud.repository.create | A repository is created |
project.crusoe-cloud.repository.delete | A repository is deleted |
project.crusoe-cloud.credential-check | The credential is re-checked (the POST, not the GET) |
project.inference.mint | An 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
| Method | Path | Who |
|---|---|---|
| GET | /v1/projects/{projectID}/crusoe-cloud | Project member |
| PUT | /v1/projects/{projectID}/crusoe-cloud | Project resource-admin |
| DELETE | /v1/projects/{projectID}/crusoe-cloud | Project resource-admin |
| GET | /v1/projects/{projectID}/crusoe-cloud/buckets | Project member |
| POST | /v1/projects/{projectID}/crusoe-cloud/buckets | Project 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/repositories | Project member |
| POST | /v1/projects/{projectID}/crusoe-cloud/repositories | Project resource-admin |
| DELETE | /v1/projects/{projectID}/crusoe-cloud/repositories/{repository} | Project resource-admin |
| POST | /v1/projects/{projectID}/crusoe-cloud/inference-key | Project resource-admin |
| GET | /v1/projects/{projectID}/crusoe-cloud/models | Project member |
| GET | /v1/projects/{projectID}/crusoe-cloud/credential-check | Project member |
| POST | /v1/projects/{projectID}/crusoe-cloud/credential-check | Project 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:
| Command | What it does |
|---|---|
platformctl crusoe-cloud connect | Store the project's Crusoe Cloud credential. Project admin only. |
platformctl crusoe-cloud show | Report whether the project is connected, and to what. |
platformctl crusoe-cloud buckets list | List 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 list | List 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 list | List the dedicated Managed Inference deployments in the connected project — the models half only, not the catalogue. |
platformctl crusoe-cloud disconnect | Remove 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.
--secret-key flag, and that is deliberateA 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
- Platform API — Crusoe Cloud integration — every route, field, and status code in reference form.
- CLI reference: platform and auth — the rest of the
platformctlcommand tree. - Triggers — wiring a bucket to a service, and the rest of the object-store trigger contract.
- Deploy a service — using a
registry_urlas a deploy image. - Secrets Manager overview — the store the mapping credential lives in.
- Security overview — the identity, roles, and 404-not-403 model these routes follow.