Story: deploy an API from the CLI, end to end
This is the full life of a small API: you write one file, deploy it, publish an address, decide who may call it and how fast, and finally put your own domain on it. Everything happens in one shell with platformctl. Budget about 20 minutes.
The CLI is the whole control plane here. Nothing in this story is clicked.
The shape of the thing
Before you begin
- A platform account and a project — see Create an account.
platformctlinstalled and signed in — see Install the CLI.- Your project connected to Crusoe Cloud. Everything you deploy is built into your own registry; a project with no credential is refused before anything is built. Check with
platformctl crusoe-cloud show. If it says not connected, see connect your Crusoe Cloud account.
Step 1: the function, in one file
An API here is a function: one file that receives the parsed JSON body and returns a dict.
mkdir usage-api && cat > usage-api/handler.py <<'EOF'
import time
def handle(event):
# Whatever the caller sent, tell them what arrived and when.
return {
"received": event,
"at": int(time.time()),
}
EOF
Deploy it:
platformctl functions deploy ./usage-api --name usage-api
You should see the build move through building → deploying → ready, ending with:
usage-api is ready at https://usage-api-ab12cd.apps.codyhill.dev
That address is the workload's own. It answers to you — you are signed in, and the control plane knows it. It is not yet a public API, because nothing here is public until you publish it.
Call it once, through the control plane:
platformctl invoke usage-api '{"hello": "world"}'
{"received":{"hello":"world"},"at":1771234567}
Step 2: publish it
platformctl gateway publish function/usage-api --name usage --auth apikey
One command: the argument is <type>/<resource>, the endpoint is named usage, and --auth apikey means callers must send a key you issue. Because an endpoint that checks keys but holds none cannot serve, the publish refuses to leave you unprotected — it issues the first key and prints it. Copy it now: keys are shown exactly once.
You should see:
url: https://usage-ab12cd.apps.codyhill.dev
...
Anyone presenting one of this endpoint's API keys can call this.
Test the door both ways:
# No key — refused at the edge, your function never runs
curl -s -X POST "https://usage-ab12cd.apps.codyhill.dev" -d '{}'
# With the key — reaches the function
curl -s -X POST "https://usage-ab12cd.apps.codyhill.dev" \
-H "X-API-Key: <the-key>" -H "Content-Type: application/json" \
-d '{"hello": "world"}'
The refusal happens before your code. A rejected caller costs you nothing and never appears in your function's logs.
If you lost the key — you will, everyone does — issue another:
platformctl gateway key issue usage
Keys are per-endpoint, revocable one at a time. When a laptop leaves the company, revoke its key; the other callers keep working.
Step 3: slow a caller down
A partner script in a retry loop can turn one bug into your whole capacity. Cap it:
platformctl gateway endpoint update usage --rate-limit 100/minute
Past the limit, the caller gets a refusal at the door — again, before your function. The limit is per caller, so one noisy integration does not starve the rest. See rate limits.
Step 4: close it to the networks you trust
If the API is for one office or one partner, say so:
platformctl gateway endpoint update usage --allow-cidr 203.0.113.0/24
Repeat the flag to allow several ranges. A caller from anywhere else is refused with no key check consulted — the address-range check runs at the edge, before anything of yours is asked. See address allow lists.
Stack the two protections and the checks are both cheap and both early: wrong network? refused. Right network, no key? refused. Right network, right key, too fast? refused. Only the fourth caller reaches your function.
Step 5: put your own name on it
The default address works but reads like a machine name. Claim a domain you own, then publish on it:
platformctl gateway domain claim api.example.com
# the command prints a DNS record to create — one TXT for proof, one CNAME for traffic
platformctl gateway publish function/usage-api --name usage --auth apikey --domain api.example.com
The address is chosen when the endpoint is published, so moving to your own name is a re-publish rather than an edit. The claim prints the DNS records to create; once the proof resolves, the endpoint answers on https://api.example.com. The mechanics — order of operations, the failure states, unclaiming — are on custom domains.
Step 6: change the code, keep the address
Edit handler.py — return something different — and redeploy:
platformctl functions deploy ./usage-api --name usage-api
A new revision appears: a frozen snapshot of code and settings. Traffic keeps flowing to the old one until the new one is ready, and rollback is selecting the older revision, not redeploying. The published endpoint never moves — it points at the function, not at a revision. See traffic and revisions.
What you just used, and where to read more
| You used | The page that documents it |
|---|---|
| Functions and the handler contract | Functions overview, HTTP and events |
| Publishing and keys | Publish an endpoint, Authentication |
| Rate limits | Rate limits |
| Address allow lists | Address allow lists |
| Custom domains | Custom domains |
| Revisions and rollback | Traffic and revisions |
Next: stop calling the API yourself. Build a real-time chat monitor wires a topic to a function so the platform calls it for you.