Skip to main content

API reference guide

Credentials and authentication.

qURL has three credentials, and you only ever manage one of them. This page explains what each one proves, how to create the two you can create, and what scopes decide — which is permissions, and only permissions.

All examples call https://api.layerv.ai/v1.

The model

One credential you manage. Two the platform hands you.

Every credential in your account reports its kind. That field is the whole model: it tells you what the credential is, how long it lives, and who was allowed to create it.

01

API keykind: api_key

The durable account credential you manage. It calls the API, and its scopes are permissions — they decide what the key may do, never what the key is. An API key can also enroll an agent interactively: the platform emails a one-time code to your account and the agent returns it. With the qurl:agent scope, the key can mint enrollment tokens.

02

Enrollment tokenkind: enrollment_token

A single-use credential that enrolls exactly one target — an agent or a connector. It is the only headless enrollment path: no mailbox is involved, because the token was minted for one enrollment and is its own proof. It can be limited to one resource claim, so a token minted for the connector my-app can enroll nothing else.

03

Device credentialkind: device

When an agent or connector finishes enrolling, the platform mints this and the machine stores it. From that point on it is that machine’s identity: restarts reuse it, and no enrollment token or emailed code is needed again. It appears in credential listings so you can see and revoke it; it can never be created through the API.

API key

Lifetime: Durable — no expiry
Proven by: Proof of account ownership (console sign-in, or an emailed one-time code)
Created by: You, in the console

Enrollment token

Lifetime: One-shot, 24 hours maximum
Proven by: The token itself
Created by: You, from the console or an API key holding qurl:agent

Device credential

Lifetime: Durable — the machine keeps it
Proven by: Issued by the platform at the end of a successful enrollment
Created by: The platform, never you

The rule behind the model

Durable and general → the platform emails a code. Short-lived and purpose-built → the credential is its own proof.

An API key is durable and can do many things, so creating one — or enrolling a machine with one — has to prove the account really wants it. That proof is a one-time code the platform emails to the account.

An enrollment token is the opposite: it exists for one enrollment, expires within a day, and can be pinned to a single resource. There is nothing left to prove, so no code is emailed and no mailbox has to exist. That is why it is the only credential that works on a machine nobody can read mail on.

Creating credentials

One endpoint, and kind picks what you get.

Both customer-creatable credentials come from POST /v1/api-keys. The plaintext secret is returned once, in the create response, and never again — store it before you close the terminal.

An API key

Create API keys from the console, where your sign-in is the proof of account ownership. An existing API key cannot create another API key — that restriction is what stops a leaked key from quietly minting itself a wider one.

scopes is required here and lists exactly what the key may do.

POST /v1/api-keys
curl -X POST https://api.layerv.ai/v1/api-keys \
  -H "Authorization: Bearer $LAYERV_CONSOLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "api_key",
    "name": "Production integration",
    "scopes": ["qurl:read", "qurl:write"]
  }'
201 Created
{
  "data": {
    "key_id": "key_abc123def456",
    "api_key": "lv_live_a3x9Kp2mN8qR5sT7wY0zBcDfGhJkLmNp",
    "key_prefix": "lv_live_a3x9",
    "kind": "api_key",
    "name": "Production integration",
    "scopes": ["qurl:read", "qurl:write"],
    "status": "active",
    "created_at": "2026-08-04T10:30:00Z"
  },
  "meta": { "request_id": "req_abc123" }
}

An enrollment token for a connector

Set target: connector and bind the token to the connector id with one claim. A bound token can enroll that connector and nothing else, so it is safe to hand to whoever runs the install.

An API key can mint this if it holds qurl:agent. The connector id is 3 to 64 characters: lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit.

POST /v1/api-keys
curl -X POST https://api.layerv.ai/v1/api-keys \
  -H "Authorization: Bearer $QURL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "enrollment_token",
    "name": "prod-dashboard connector enrollment",
    "target": "connector",
    "claims": [{ "type": "connector", "id": "prod-dashboard" }],
    "expires_in": "2h"
  }'
201 Created
{
  "data": {
    "key_id": "key_def456abc123",
    "api_key": "lv_live_b7k2...",
    "key_prefix": "lv_live_b7k2",
    "kind": "enrollment_token",
    "target": "connector",
    "claims": [{ "type": "connector", "id": "prod-dashboard" }],
    "name": "prod-dashboard connector enrollment",
    "scopes": ["qurl:agent", "qurl:write"],
    "status": "active",
    "created_at": "2026-08-04T10:30:00Z",
    "expires_at": "2026-08-04T12:30:00Z"
  },
  "meta": { "request_id": "req_def456" }
}

An enrollment token for an agent

Set target: agent and leave claims out for a token that can enroll any one agent. Add a connector claim if you want the agent restricted to a single connector.

expires_in is optional and defaults to 24 hours, which is also the ceiling. Mint the token close to when it will be used.

POST /v1/api-keys
curl -X POST https://api.layerv.ai/v1/api-keys \
  -H "Authorization: Bearer $QURL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "enrollment_token",
    "name": "headless agent enrollment",
    "target": "agent"
  }'

Things the endpoint will not do

  • It will not accept scopes on an enrollment token. Token scopes are server-assigned from target: an agent token gets qurl:agent, a connector token gets qurl:agent and qurl:write.
  • It will not accept target, claims, or expires_in on an API key. API keys are durable and general by definition.
  • It will not create a device credential. Those are minted by the platform when an enrollment completes.
  • It will not let an enrollment token mint anything. A one-shot credential cannot create credentials.
  • It will not accept more than one claim. One claim is the supported shape today.

Seeing what you have

GET /v1/api-keys lists every credential on the account, including the device credentials the platform minted for your machines. The plaintext secret is never in a list response.

Names and scopes are editable on an API key. An enrollment token’s scopes are server-assigned and a device credential cannot be edited at all; a credential’s kind never changes.

GET /v1/api-keys
curl https://api.layerv.ai/v1/api-keys \
  -H "Authorization: Bearer $LAYERV_CONSOLE_TOKEN"
200 OK
{
  "data": [
    {
      "key_id": "key_abc123def456",
      "key_prefix": "lv_live_a3x9",
      "kind": "api_key",
      "name": "Production integration",
      "scopes": ["qurl:read", "qurl:write"],
      "status": "active"
    },
    {
      "key_id": "key_ghi789jkl012",
      "key_prefix": "lv_live_c4m8",
      "kind": "device",
      "name": "prod-dashboard (host-7)",
      "scopes": ["qurl:agent", "qurl:write"],
      "status": "active"
    }
  ],
  "meta": { "request_id": "req_ghi789", "page_size": 2, "has_more": false }
}

Scopes

Scopes are permissions. They are not a credential type.

A scope narrows what an API key may do. It never changes what the key is: an API key with qurl:agent is an ordinary durable API key that can also mint enrollment tokens. For a credential that is narrow in time and pinned to one resource, reach for an enrollment token, not a scope.

qurl:read

Read qURLs, resources, and quota.

qurl:write

Create, update, and delete qURLs and resources. Also covers webhook management.

qurl:resolve

Redeem access tokens headlessly and share resources by CRID.

qurl:agent

Mint enrollment tokens. An API key without this scope cannot create an enrollment token for either target.

Who may mint what

  • Console callers may create both an API key and an enrollment token.
  • An API key holding qurl:agent may create enrollment tokens for either target — and nothing else.
  • An API key without qurl:agent may not create any credential.
  • An enrollment token may not create any credential.

Which one do I want?

Start from the job, not the credential.

I want to call the API from my backend.

An API key. Create it in the console, give it only the scopes that backend needs, and store it in your secret manager.

I am starting a service or agent and something can read a mailbox.

An API key plus the emailed code. This is the default path and needs nothing minted in advance — see the Go SDK agent guide.

I am starting a service or agent and nothing can read a mailbox.

An enrollment token with target=agent. Mint it just before the run and pass it to the runtime; it expires within 24 hours whether it is used or not.

I am installing a connector next to a private service.

An enrollment token with target=connector, bound to that connector id with a claim. The connector consumes it on first start.

My machine already enrolled and is restarting.

Nothing. It holds a device credential. Do not mint a second enrollment token for a machine that already enrolled.

I want one credential for a CI job that only reads.

An API key scoped to qurl:read. Scopes are how you narrow an API key; there is no smaller durable credential.

Then follow the guide for that machine

The credential is only the first step. Both onboarding guides pick up from the credential you just chose.

The create request

The API is kind-first. kind decides the rest.

POST /v1/api-keys takes kind on every create, and validates every other field against it. A create request without `kind` is rejected, and so is `key_type` — it is not a field this API accepts. If a client is sending it, this is the shape to send instead.

kind

Required on every create. `api_key` or `enrollment_token`. It is the discriminator: every other field is accepted or rejected based on it.

name

Required. A human-readable label, up to 100 characters.

scopes

Required for `kind: api_key`, and accepted only there. Enrollment-token scopes are server-assigned from `target`; sending the field on a token returns 400 `invalid_input`.

target

Enrollment tokens only. `connector` or `agent` — what this token enrolls. Omit it and the API derives it from `claims`: a connector claim means `connector`, otherwise `agent`.

claims

Enrollment tokens only. Binds the token to one resource: `[{ "type": "connector", "id": "prod-dashboard" }]`. `target: connector` requires exactly one connector claim; `target: agent` takes zero or one.

expires_in

Enrollment tokens only. Defaults to 24h and cannot exceed it. API keys are durable and reject the field.

A create request in full

This one mints a connector enrollment token bound to prod-dashboard. The response returns the one-time secret in the standard envelope.

Request body
{
  "kind": "enrollment_token",
  "name": "prod-dashboard connector enrollment",
  "target": "connector",
  "claims": [{ "type": "connector", "id": "prod-dashboard" }],
  "expires_in": "2h"
}

What responses carry

  • kind is on every credential in create, get, and list responses. It is the field to switch on.
  • target and claims appear on enrollment tokens. claims is present only when the token is bound.
  • Responses carry no key_type. A client reading that field gets undefined.
  • Treat kind as an open set. New kinds may appear as the model grows, so parse unknown values without failing — switch on the kinds you handle and ignore the rest rather than rejecting the response.

Errors

What a rejected create is telling you.

Validation errors return 400 with a code in error.code. A 403 means the caller was not allowed to mint that kind — most often an API key without qurl:agent, or an API key trying to create an API key.

invalid_input

The request mixes fields that do not belong to the kind you asked for — scopes on an enrollment token, target or claims on an API key, expires_in on an API key, more than one claim, or a claim id that does not match the id format.

invalid_duration

expires_in is malformed or longer than 24 hours. The format is a number followed by s, m, h, d, or w.

bootstrap_key_consumed

Returned with 409. The enrollment token was already consumed by another enrollment, so it is terminal. Mint a new one; never retry the same token concurrently.

api_key_limit

The account reached its plan credential limit. Check the quota response for the current allowance.

email_verification_required

The account email is not verified yet, so it cannot be proven for a durable credential.

Next

Pick a credential, then enroll something.

The full request and response schemas, including every field and status code, live in the API reference.