Get a write key
Available now
Reading OHM needs nothing. Browsing designs, searching facilities, running a match — all of it works with no credential at all. Writing is the part that needs a key: saving a facility, adding a design, importing a collection.
This page is about getting that key on a node you run.
Which node are you writing to?
Two very different situations, and the difference matters more than anything else on this page.
Someone else's node — register yourself, if the node lets you. Most do:
open registration is on in both peacetime and crisis postures, and off only
in shielded. Go to /register, pick a display name, and the node mints you an
account and a key on the spot. See Join a node.
If registration is closed, the node's operator issues credentials instead. Ask them.
Your own node — you already have everything you need. The rest of this page is for you.
First, a warning worth reading
API_KEYS is not, on its own, a switch that turns write protection on.
Whether writes are checked depends on the node's posture, which comes from
ENVIRONMENT:
ENVIRONMENT |
Anonymous write | Why |
|---|---|---|
development (the default) |
Accepted | Dev and test flows stay frictionless |
production |
Rejected, 401 |
Write auth enforced |
That first row surprises people. On a development node, an anonymous POST
succeeds even with API_KEYS set — the key is accepted if you send it, and
not required if you don't.
So if your node is reachable by anyone but you, setting a key is not enough:
Crisis and shielded security modes always enforce writes, whatever ENVIRONMENT
says. See Security Modes.
One more thing production needs
A node in production refuses to start without OHM_ENCRYPTION_SALT and
OHM_ENCRYPTION_PASSWORD, which protect stored language-model credentials.
Generate them once:
echo "OHM_ENCRYPTION_SALT=$(openssl rand -hex 16)" >> .env
echo "OHM_ENCRYPTION_PASSWORD=$(openssl rand -hex 32)" >> .env
Without them the container exits on boot with a message naming both.
Step 1 — the bootstrap credential
A new node has no users, so the first credential comes from the environment:
Check it worked. whoami tells you who the node thinks you are:
{
"key_id": "00000000-0000-0000-0000-000000000000",
"name": "Environment Key",
"permissions": ["read", "write", "admin"],
"account_id": "00000000-0000-0000-0000-000000000001",
"subject_did": null
}
The all-zero key_id is the tell: this credential comes from the environment,
not from storage. It carries admin, so it can create everything else.
Without a token the same call returns:
Step 2 — create a named key
You can write with the bootstrap token. You should not, for the same reason
you don't hand out the root password: it is admin-scoped, it lives in your
.env, and rotating it means restarting the node.
Make a narrower key instead:
curl -s -X POST http://localhost:8001/v1/api/identity/keys \
-H "Authorization: Bearer $YOUR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"name": "facility-editor", "permissions": ["read", "write"]}'
{
"key_id": "e987bb03-98dc-4562-bd8c-1be360998b2b",
"name": "facility-editor",
"permissions": ["read", "write"],
"created_at": "2026-08-29T04:04:08.532361",
"revoked": false,
"token": "cG0D1wKnYMwClHWv-Xw4yLYEwhhVyEWEfOdf3R5kD3Q"
}
Copy token now. It is returned exactly once. Ask for the key again and the
field is empty:
That is deliberate — the node stores a hash, not the secret. Lose it and you create another key; there is no recovery path, and that is the point.
In the web interface the same thing lives under Settings → Keys & accounts,
visible when your current credential carries admin.
Step 3 — use it
Send it as a bearer token:
curl -s -X POST http://localhost:8001/v1/api/okw/create \
-H "Authorization: Bearer $WRITE_KEY" \
-H 'Content-Type: application/json' \
-d '{"content": {
"name": "Lab Test Workshop",
"location": {"address": {"city": "Portland", "country": "US"}},
"facility_status": "Active"
}}'
201 and the stored facility comes back with its id. Note the content
wrapper — the facility goes inside it.
In the web interface, paste the token into Settings → Session and the browser sends it for you. That is what the facility form means when it says you need a write key before saving.
Step 4 — revoke it when you're done
curl -s -X DELETE http://localhost:8001/v1/api/identity/keys/$KEY_ID \
-H "Authorization: Bearer $YOUR_API_KEY"
The next write with that token is refused:
and the key stays in the listing with "revoked": true, so the record of what
existed does not disappear along with the access.
What to expect, in one table
Measured on a node running 0.11.1 with ENVIRONMENT=production:
| Request | Result |
|---|---|
| Read, no credential | 200 — reads are open |
| Write, no credential | 401 |
| Write, invalid token | 401 |
| Write, valid write key | 201 |
| Write, revoked key | 401 |
On ENVIRONMENT=development, the second row is 201 instead. If that is not
what you want, see the warning above.
Where to go next
- Use the OHM API — the wider API surface
- Run your own node — securing a node before it is reachable
- Who can see your data — visibility, which is a separate question from write access