DK Studio/Documentation
ProductChangelog

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.

TypeWho issues itWhat the machine becomes
personalThe person themselves, from their own Personal CenterA machine that belongs to that person.
labAn administrator, from Admin → LicencesA 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 licence is the only way in

A machine never applies for itself. It needs a licence code, and the code says what it will become.

The registration flow

  1. 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_licenses with status pending and returns a licence code.

  2. 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.

  3. The agent registers

    POST /api/agent/register presents 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, apiKey and agentSecret.

  4. The agent authenticates on the socket

    The agent opens a Socket.IO connection to the studio and sends an auth request with agentId, agentSecret and protocolVersion. The studio answers with an auth response whose data holds agentId, name, capabilities and protocolVersion. A mismatch produces ok: false with the code AUTH_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.

GET/api/agent/licenseLook up a licence by code. Returns its type, scope and whether it can still be used.
POST/api/agent/registerClaim a licence. 403 when the licence is unknown, revoked, expired or already used.
GET/api/licensesList the licences you own and the agents you can see.

Entitlements

Two entitlements govern who may run on the install, and they belong to different keys.

EntitlementWhere it livesWhat it grants
The studio licence.studio-license.jsonmaxAgents bounds how many agents the install accepts; features decides which capability modules are available.
The agent licenceagent_licensesOne 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.

CredentialShapeWhere it is presentedWhat the studio checks
apiKey32 alphanumeric charactersThe body of POST /api/agent/authagents.api_key
agentSecret64 hex charactersThe payload of the socket message authagents.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>/revoke

The 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>.

Revoking does not cut a live socket

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

SymptomLikely causeWhat to check
403 invalid_codeThe code was mistyped, or the licence does not exist.Copy the code from the Personal Center or from Admin → Licences.
403 usedThe licence was already spent on another machine.The status column in the licence list reads consumed.
403 revokedThe licence was revoked earlier.The status column in the licence list.
403 expiredThe expiry you set has passed.Issue a new licence.
404 on GET /api/agent/licenseThe code does not match any licence.Check for a truncated paste.
500 Database not configuredThe 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 onlineThe 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_FAILEDThe 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.