Agent registration and entitlements
An agent joins a studio by claiming a licence. The licence is a one-shot ticket: it says who the machine belongs to, and it is spent the moment the machine registers.
The two licence types
A licence carries its own type, and the type decides the ownership of the machine that presents it.
| Type | Who issues it | What the machine becomes |
|---|---|---|
personal | The person themselves, from their own Personal Center | A machine that belongs to that person. |
lab | An administrator, from Admin → Licences | A bench that belongs to the shared lab. |
Where the machine physically sits makes no difference. A personal licence used on a bench in the lab
still registers a personal machine — the person owns it, wherever it happens to be plugged in. The
location field on a lab licence is a note for the humans, not a rule for the system.
A machine never applies for itself. It needs a licence code, and the code says what it will become.
The registration flow
- A licence is issued
A person signs in to the studio and applies for a personal licence in their Personal Center, or an administrator issues a lab licence. The studio creates a row in
agent_licenseswith statuspendingand returns a licence code. - The agent checks the code
GET /api/agent/license?code=…answers with the licence's type and scope, so the operator can see whether the machine will be personal or lab before registering it. - The agent registers
POST /api/agent/registerpresents the code plus a name. The studio validates the licence against its status and expiry, creates the agent in the scope the licence type dictates, and returns the agent's permanent identity:agentId,apiKeyandagentSecret. - The agent authenticates on the socket
The agent opens a Socket.IO connection to the studio and sends an
authrequest withagentId,agentSecretandprotocolVersion. The studio answers with anauthresponse whosedataholdsagentId,name,capabilitiesandprotocolVersion. A mismatch producesok: falsewith the codeAUTH_FAILED, after which the studio disconnects the socket.
The licence moves from pending to consumed when the agent claims it. A consumed licence never
works again — presenting the same code a second time is refused.
Issuing a personal licence
Every signed-in person issues their own personal licences. In the studio, open Personal Center and use the Apply for a license action. The code appears once; copy it and enter it on the machine.
The same thing over HTTP:
curl -X POST http://studio.lab.internal:3000/api/licenses \
-H 'Content-Type: application/json' \
-H "Cookie: session=$SESSION" \
-d '{"type":"personal"}'{
"success": true,
"license": {
"id": "0f2c9d1e-…",
"code": "dk_l_…40 hex characters…",
"type": "personal",
"ownerUserId": "9a1c…",
"location": null,
"label": null,
"status": "pending",
"expiresAt": null,
"createdAt": "2026-11-02T09:14:00.000Z"
}
}The licence belongs to the person who applied for it. Nobody else can see the code — a licence is the ticket, and only its owner and an administrator can read it.
Issuing a lab licence
Administrators issue lab licences for the shared benches from Admin → Licences. A lab licence carries a location and a label, and it belongs to the lab rather than to a person.
curl -X POST http://studio.lab.internal:3000/api/licenses \
-H 'Content-Type: application/json' \
-H "Cookie: session=$SESSION" \
-d '{"type":"lab","location":"A-12","label":"Bench A"}'Registering the machine
On the bench, open the agent's page at http://localhost:3001, fill in the form and submit. The
form asks for the studio URL, the agent name and the licence code. The Check license button asks
the studio what kind of licence it is and shows the answer before anything is registered.
The agent sends:
{
"code": "dk_l_…40 hex characters…",
"name": "bench-01",
"studioUrl": "http://studio.lab.internal:3000",
"workstation": { "hostname": "bench-01", "platform": "linux", "arch": "x64" },
"capabilities": []
}The studio answers:
{
"success": true,
"agentId": "6d1f…",
"apiKey": "…32 alphanumeric characters…",
"agentSecret": "…64 hex characters…",
"licenseType": "personal",
"scope": "personal",
"ownerUserId": "9a1c…",
"location": null,
"reRegistered": false
}The agent writes these to data/agent.json on the bench, and then opens the socket.
Entitlements
Two entitlements govern who may run on the install, and they belong to different keys.
| Entitlement | Where it lives | What it grants |
|---|---|---|
| The studio licence | .studio-license.json | maxAgents bounds how many agents the install accepts; features decides which capability modules are available. |
| The agent licence | agent_licenses | One seat for one machine. It carries type (personal or lab), status (pending, consumed, revoked or expired) and expires_at. |
The studio licence is checked when the install is set up and whenever an agent claims a seat. The agent licence is checked at registration: an unknown, revoked, expired or already-consumed licence is refused with 403. See Self-hosted deployment for the editions and their allowances.
Issue one licence per machine. A licence is claimed once, and a licence shared across a fleet makes revocation meaningless.
The credentials
A registration produces two credentials, and they are used in different places.
| Credential | Shape | Where it is presented | What the studio checks |
|---|---|---|---|
apiKey | 32 alphanumeric characters | The body of POST /api/agent/auth | agents.api_key |
agentSecret | 64 hex characters | The payload of the socket message auth | agents.agent_secret |
Neither is ever sent in a header. There is no Authorization header, no X-API-Key header and no
bearer token anywhere in the product; the credentials travel in request bodies and socket payloads.
The agent never calls POST /api/agent/auth in normal operation — the endpoint exists for scripted
use and returns the agent's id, name and capabilities. The socket handshake checks only the
agentSecret: an agent that sends a valid agentId with a wrong secret gets an AUTH_FAILED
answer and is disconnected.
Revoking access
To withdraw a licence, revoke it:
curl -X POST http://studio.lab.internal:3000/api/licenses/<id>/revokeThe licence status becomes revoked. The next POST /api/agent/register with that code is refused
with 403. The owner of a licence may revoke their own; an administrator may revoke any.
A licence that has already been consumed cannot be revoked — the machine is on the ledger and points
at that licence. To take that machine out of service, delete the agent itself with
DELETE /api/admin/agents/<id>.
Revocation is enforced at registration time. An agent that is already connected keeps its socket, keeps receiving commands and stays visible as online until it disconnects or the studio restarts. To take a bench out of service immediately, restart the agent process as well.
Troubleshooting registration
| Symptom | Likely cause | What to check |
|---|---|---|
403 invalid_code | The code was mistyped, or the licence does not exist. | Copy the code from the Personal Center or from Admin → Licences. |
403 used | The licence was already spent on another machine. | The status column in the licence list reads consumed. |
403 revoked | The licence was revoked earlier. | The status column in the licence list. |
403 expired | The expiry you set has passed. | Issue a new licence. |
404 on GET /api/agent/license | The code does not match any licence. | Check for a truncated paste. |
500 Database not configured | The studio has no .db-config.json, or the database is unreachable. | GET /api/system/status — databaseConfigured must be true. |
| Registration succeeds, the agent never appears online | The agent cannot reach the studio's Socket.IO endpoint, or the studio is not running. | Work through the connectivity checklist in Network requirements. |
The agent connects, then is dropped with AUTH_FAILED | The agentSecret does not match the one the studio holds for that agentId. | Register again with the agent's own credentials so the stored secret is refreshed, then restart the agent. |
More on connectivity in Agent offline and in Network requirements.
Related pages
- Self-hosted deployment — running the studio.
- Network requirements — ports and outbound access.
- Personal Center — issuing and revoking your own licences.
- The agent pool — managing a fleet of registered agents.
- Socket protocol — the message envelope and handshake.
- REST API reference — every endpoint.