Skip to content
DocsAll docs
Console

API tokens

Create, use, and revoke the tokens that open the Kode API for a script, a CI job, or an integration.

Last updated

On this page

Overview

An API token opens the /v1 API without a browser login. A CI job, a server that uses an SDK, and a third-party integration use it. You make a token, choose what it reaches and what it can do, and the platform shows the secret once.

A token starts with klt_. The platform stores only a digest of the secret, so a lost token must be revoked and replaced. Only you can list or revoke the tokens that you made.

A token acts as you, inside its limits. It can never do more than you can do. If your role in a team falls, the token falls with it.

Reach

The reach is the owner of the token. You set it when you make the token and cannot change it.

ownerThe token reaches
Not set, or your handleYour personal resources.
A team handleThe resources of that one team. You must be an active member.
*Your personal resources and the resources of every team that you belong to. The platform checks your membership at each request.

A team that you join later is in the reach of a * token. A team that you leave or that pauses you is not.

Parts and verbs

A token holds verbs. There are nine, in three parts. No verb includes another: a token that can update a pipeline cannot start a run of it.

PartVerbWhat it means
MonitoringreadGet and list records. It includes quota and regions.
MonitoringlogsRead the output of a capsule and the job logs of a run. read does not include it.
Resource managementcreateMake a new workspace, environment, desk, or pipeline.
Resource managementupdateChange a resource that exists: its packages, a new pipeline version, the environment of a desk, an intercept.
Resource managementlifecycleStart, stop, restart, or terminate a resource, or wait for a capsule.
Resource managementsnapshotTake, clone, restore, or push a snapshot, or add a version to a volume.
Resource managementdeleteRemove a resource. It is its own tick and is off in every preset.
ExecutionsrunRun code: create a capsule, start a pipeline run, or cancel a run.
ExecutionsexecAttach to a running capsule. It is its own tick and is off in every preset.

A kind admits only the verbs that mean something for it. A grant that names another verb is refused with 422.

kindVerbs it admits
workspaces, environmentsread, create, update, lifecycle, snapshot, delete
desksread, create, update, delete
capsulesread, logs, run, lifecycle, exec, delete
pipelinesread, create, update, delete
pipeline-runsread, logs, run
volumesread, snapshot, delete
snapshotsdelete
quota, regionsread

The kind pipelines is the definitions and their versions. The kind pipeline-runs is the runs of a pipeline, their logs, and the cancel of a run. A selector on pipeline-runs matches the name of the pipeline of the run.

Presets

Set access to a list of parts. The platform expands each part into one grant on every kind in reach, with the verbs of that part that the kind admits. The expansion leaves out delete and exec.

access valueVerbs it gives
monitoringread, logs
managementcreate, update, lifecycle, snapshot
executionsrun
destroydelete, on every kind that admits it
execexec, on every kind that admits it

Use any subset, for example ["monitoring", "management"]. In the console, three switches set the parts: Monitoring, Resource management, and Executions. Two ticks, Include delete and Include exec, add destroy and exec.

Send access or grants, never both. The platform stores the expanded grants. Use grants for control of one kind or one resource.

Grants

A grant has a kind, a list of verbs, and a list of resources. It can also have own and, on capsules, limits. A request passes when one grant covers its kind, its verb, and the resource that it names.

PartValues
kindworkspaces, environments, desks, capsules, pipelines, pipeline-runs, volumes, snapshots. quota and regions are read-only and take no selector.
verbsThe verbs that the kind admits.
resources* for all, an exact id or name, or a prefix that ends in *, such as vendor-*.
owntrue to cover only what this token created.
limitsThe limits of a capsule create.

A selector matches the id or the name of the resource. The prefix is literal: vendor-* matches vendor-a and not vendors-a. A * inside a selector is not a wildcard.

A route that names a second resource needs a grant for it. An example is a desk that connects to an environment. A missing grant on one of them is a 401.

Only what the token created

With own: true, a grant covers an object only when this token created it. The platform stamps the label k-ode.dev/created-by-token on each workspace, environment, desk, capsule, pipeline, and run that a token makes. Two integrations in one team cannot see or stop the capsules and runs of each other.

A create with no name is permitted when the grant has own, because the token can then touch only what it made. own is refused on volumes and snapshots, which a token never creates.

Limits of executions

A capsules grant that holds run or exec can carry limits. A create that is outside the limits is refused with 401.

LimitMeaningWhen not set
imagesImage selectors that the image of the create must match. * permits any image.Any image. When limits is set without images, only the platform default image.
max_timeout_sThe longest timeout_secs of a create, up to 86400.3600
networkfalse refuses a create that asks for network.true

The cap of runs in flight, max_live_runs, counts the live capsules and pipeline runs that one token started. The range is 1 to 50 and the default is 5. The next start gets 429 with this token has {n} runs in flight; the limit is {max}. Another run can start when one ends. The cap is a limit on cost for a token that leaks. It is not a rate limit.

Who started a run

A pipeline run or a capsule that a token starts records the token. The record holds apiToken with the name and the first eight characters of the id. The console shows API token {name} and never the handle of the creator.

A manual start of a pipeline takes an optional key. A second start with the same key answers the run that the first start made, so a retry cannot start a duplicate. Each token and each person has its own keys.

Secrets

A token cannot send a request that names a secret. A request is refused with 401 when a string in its body holds ${{ secrets., at any depth, or when a pipeline definition has secrets. A token cannot read a secret value.

A token can start a pipeline that uses a secret, if someone else wrote the pipeline. The pipeline uses the secret. The holder of the token never receives it.

Limits

LimitValue
Name1 to 60 characters
Expiry1 to 365 days; 90 when not set
Grants in one token20
Selectors in one grant50
Runs in flight1 to 50; 5 when not set
Live tokens for one creator and one owner25

A token always expires. The list shows the expiry and the time of the last use. The platform records the last use at most once an hour.

What a token cannot do

A token cannot reach the routes for tokens, keys, teams, invitations, secrets, connectors, requests, sign-in, or the console administration. It cannot read a secret value. It cannot open a tool server or an SSH session. exec reaches the attach route of a capsule and no other route. These routes answer 401 missing or invalid token, the same answer as a wrong token.

A request for a resource outside the grants is the same 401. A request with ?owner= or team for a team that a one-team token does not serve is 403 with this token acts only for and the team.

How lists are narrowed

A list route answers only the items that the grants select. A grant with * and no own returns the whole page. A grant with selectors or own removes the other items from the page, so a page can be short or empty while more pages follow. Follow the x-next-cursor header until it is absent.

Tasks

Create an API token

Reason for the task

Give a program read access, and the right to manage resources, in one owner.

Job set-up

  • Access: you, signed in with the console or a CLI login. A token cannot make a token. A team token needs an active team member.
  • References: POST /v1/tokens.
WARNING

The secret opens the API within the reach and the parts. Keep it out of repositories and logs.

Procedure

  1. Create the token with name, owner, and access. In the console, select New token in Profile settings.

    Python
    t = kl.request("POST", "/v1/tokens", body={    "name": "ci",    "owner": "acme",    "access": ["monitoring", "management"]})
    TypeScript
    const t = (await kl.request("POST", "/v1/tokens", {  name: "ci",  owner: "acme",  access: ["monitoring", "management"]})) as { _id: string; token: string };
    Go
    var t map[string]anyerr := kl.Request("POST", "/v1/tokens", kloudlite.RequestOptions{    Body: map[string]any{        "name": "ci",        "owner": "acme",        "access": []string{"monitoring", "management"},    },    Out:  &t,})if err != nil {    log.Fatal(err)}
    Ruby
    t = kl.request("POST", "/v1/tokens", body: {  name: "ci",  owner: "acme",  access: ["monitoring", "management"]})
    Java
    String t = kl.request(new Kloudlite.RawRequest().method("POST").path("/v1/tokens")    .body("{\"name\":\"ci\",\"owner\":\"acme\",\"access\":[\"monitoring\",\"management\"]}"));
    CLI
    kode request POST /v1/tokens --body '{"name": "ci", "owner": "acme", "access": ["monitoring", "management"]}'
  2. Put the token value in your secret store. The platform does not show it again.

Close-out

  1. Show the tokens as in task 10-20-00-005. Make sure that the new token is listed.

Create a token that runs one pipeline

Reason for the task

Give a vendor the right to run the pipeline nightly-report and to read its runs and logs, and nothing else.

Job set-up

  • Access: you. The owner is a team where you are an active member.
  • References: POST /v1/tokens.
CAUTION

The vendor can start nightly-report and read its logs. A log can hold data that the pipeline prints. Check what the pipeline prints.

Procedure

  1. Create the token with one grant on pipeline-runs for read, logs, and run. Set two runs in flight and 30 days.

    Python
    t = kl.request("POST", "/v1/tokens", body={    "name": "vendor-report",    "owner": "acme",    "expires_in_days": 30,    "max_live_runs": 2,    "grants": [        {            "kind": "pipeline-runs",            "verbs": ["read", "logs", "run"],            "resources": ["nightly-report"]        }    ]})
    TypeScript
    const t = (await kl.request("POST", "/v1/tokens", {  name: "vendor-report",  owner: "acme",  expires_in_days: 30,  max_live_runs: 2,  grants: [    {      kind: "pipeline-runs",      verbs: ["read", "logs", "run"],      resources: ["nightly-report"]    }  ]})) as { _id: string; token: string };
    Go
    var t map[string]anyerr := kl.Request("POST", "/v1/tokens", kloudlite.RequestOptions{    Body: map[string]any{        "name": "vendor-report",        "owner": "acme",        "expires_in_days": 30,        "max_live_runs": 2,        "grants": []map[string]any{            {                "kind": "pipeline-runs",                "verbs": []string{"read", "logs", "run"},                "resources": []string{"nightly-report"},            },        },    },    Out:  &t,})if err != nil {    log.Fatal(err)}
    Ruby
    t = kl.request("POST", "/v1/tokens", body: {  name: "vendor-report",  owner: "acme",  expires_in_days: 30,  max_live_runs: 2,  grants: [    {      kind: "pipeline-runs",      verbs: ["read", "logs", "run"],      resources: ["nightly-report"]    }  ]})
    Java
    String t = kl.request(new Kloudlite.RawRequest().method("POST").path("/v1/tokens")    .body("{\"name\":\"vendor-report\",\"owner\":\"acme\",\"expires_in_days\":30,\"max_live_runs\":2,\"grants\":[{\"kind\":\"pipeline-runs\",\"verbs\":[\"read\",\"logs\",\"run\"],\"resources\":[\"nightly-report\"]}]}"));
    CLI
    kode request POST /v1/tokens --body '{"name": "vendor-report", "owner": "acme", "expires_in_days": 30, "max_live_runs": 2, "grants": [{"kind": "pipeline-runs", "verbs": ["read", "logs", "run"], "resources": ["nightly-report"]}]}'
  2. Give the token value to the vendor through your secret store.

  3. Give the vendor the rule to send a key with each start, such as the date. A retry with the same key answers the first run.

Close-out

  1. Show the tokens. Make sure that the grants of the new token name only pipeline-runs.

Create a token that runs capsules from one set of images

Reason for the task

Give a CI job the right to run capsules from node: images, for at most 600 seconds, and to see only its own capsules.

Job set-up

  • Access: you. The owner is your handle or a team where you are an active member.
  • References: POST /v1/tokens.
CAUTION

The job can run any code in a capsule that it starts. Choose the image selector with care.

Procedure

  1. Create the token with one grant on capsules that has own and limits. Set ten runs in flight.

    Python
    t = kl.request("POST", "/v1/tokens", body={    "name": "ci-node",    "owner": "acme",    "max_live_runs": 10,    "grants": [        {            "kind": "capsules",            "verbs": ["read", "logs", "run", "lifecycle"],            "resources": ["*"],            "own": True,            "limits": {                "images": ["node:*"],                "max_timeout_s": 600,                "network": True            }        }    ]})
    TypeScript
    const t = (await kl.request("POST", "/v1/tokens", {  name: "ci-node",  owner: "acme",  max_live_runs: 10,  grants: [    {      kind: "capsules",      verbs: ["read", "logs", "run", "lifecycle"],      resources: ["*"],      own: true,      limits: {        images: ["node:*"],        max_timeout_s: 600,        network: true      }    }  ]})) as { _id: string; token: string };
    Go
    var t map[string]anyerr := kl.Request("POST", "/v1/tokens", kloudlite.RequestOptions{    Body: map[string]any{        "name": "ci-node",        "owner": "acme",        "max_live_runs": 10,        "grants": []map[string]any{            {                "kind": "capsules",                "verbs": []string{"read", "logs", "run", "lifecycle"},                "resources": []string{"*"},                "own": true,                "limits": map[string]any{                    "images": []string{"node:*"},                    "max_timeout_s": 600,                    "network": true,                },            },        },    },    Out:  &t,})if err != nil {    log.Fatal(err)}
    Ruby
    t = kl.request("POST", "/v1/tokens", body: {  name: "ci-node",  owner: "acme",  max_live_runs: 10,  grants: [    {      kind: "capsules",      verbs: ["read", "logs", "run", "lifecycle"],      resources: ["*"],      own: true,      limits: {        images: ["node:*"],        max_timeout_s: 600,        network: true      }    }  ]})
    Java
    String t = kl.request(new Kloudlite.RawRequest().method("POST").path("/v1/tokens")    .body("{\"name\":\"ci-node\",\"owner\":\"acme\",\"max_live_runs\":10,\"grants\":[{\"kind\":\"capsules\",\"verbs\":[\"read\",\"logs\",\"run\",\"lifecycle\"],\"resources\":[\"*\"],\"own\":true,\"limits\":{\"images\":[\"node:*\"],\"max_timeout_s\":600,\"network\":true}}]}"));
    CLI
    kode request POST /v1/tokens --body '{"name": "ci-node", "owner": "acme", "max_live_runs": 10, "grants": [{"kind": "capsules", "verbs": ["read", "logs", "run", "lifecycle"], "resources": ["*"], "own": true, "limits": {"images": ["node:*"], "max_timeout_s": 600, "network": true}}]}'
  2. Put the token value in the secret store of the CI system.

Close-out

  1. Show the tokens. Make sure that the grant shows own and the limits.

Use a token with the CLI and the SDKs

Reason for the task

Run a script, a CI job, or an SDK client with a token and no browser login.

Job set-up

  • Access: the secret of the token.
  • References: SDKs.

Procedure

  1. Set the API origin and the token in the environment of the program. The SDKs and kode read both.

    CLI
    export KODE_URL=https://dev.kloudlite.ioexport KODE_TOKEN=klt_...
  2. Run the program. For a direct call, send the token as a bearer.

    CLI
    curl -H "Authorization: Bearer $KODE_TOKEN" https://dev.kloudlite.io/v1/workspaces

Close-out

  1. Make sure that the call answers 200. A 401 means that the token is wrong, expired, revoked, or outside its grants for that route.

List your tokens

Reason for the task

Find the tokens that you made, their reach, their expiry, and their last use.

Job set-up

  • Access: you, signed in with the console or a CLI login.
  • References: GET /v1/tokens.

Procedure

  1. Show the tokens. Add ?owner= to see the tokens of one reach.

    Python
    for t in kl.request("GET", "/v1/tokens"):    print(t["_id"], t["name"], t["reach"], t["expiresAt"], t["lastUsedAt"])
    TypeScript
    const tokens = (await kl.request("GET", "/v1/tokens")) as { _id: string; name: string; expiresAt: string; lastUsedAt: string | null }[];for (const t of tokens) console.log(t._id, t.name, t.expiresAt, t.lastUsedAt);
    Go
    var tokens []map[string]anyerr := kl.Request("GET", "/v1/tokens", kloudlite.RequestOptions{Out: &tokens})if err != nil {    log.Fatal(err)}for _, t := range tokens {    fmt.Println(t["_id"], t["name"], t["expiresAt"], t["lastUsedAt"])}
    Ruby
    kl.request("GET", "/v1/tokens").each { |t| puts [t["_id"], t["name"], t["expiresAt"], t["lastUsedAt"]].join(" ") }
    Java
    System.out.println(kl.request(new Kloudlite.RawRequest().method("GET").path("/v1/tokens")));
    CLI
    kode request GET /v1/tokens

Close-out

  1. Make sure that each token in the list is still in use. A lastUsedAt of null means that no use is recorded.

Revoke a token

Reason for the task

Stop a token that is unused or exposed.

Job set-up

CAUTION

A revoked token stops at once. Programs that use it fail.

Procedure

  1. Find the _id of the token as in task 10-20-00-005.

  2. Delete the token. Replace {id} with the _id of the token.

    Python
    kl.request("DELETE", "/v1/tokens/{id}")
    TypeScript
    await kl.request("DELETE", "/v1/tokens/{id}");
    Go
    if err := kl.Request("DELETE", "/v1/tokens/{id}", kloudlite.RequestOptions{}); err != nil {    log.Fatal(err)}
    Ruby
    kl.request("DELETE", "/v1/tokens/{id}")
    Java
    kl.request("DELETE", "/v1/tokens/{id}");
    CLI
    kode request DELETE /v1/tokens/{id}

Close-out

  1. Show the tokens. Make sure that the _id is gone.

Troubleshooting

SymptomPossible causeCorrective action
401 with missing or invalid tokenThe token is wrong, expired, or revoked, or the route, verb, or resource is outside its grants.Check the expiry and the grants in the list. Create a new token if needed.
401 on POST /v1/capsulesThe token has no run on capsules, or the create is outside its limits.Add run to the grant. Use an image, a timeout, and a network setting that the limits permit.
401 on a request with an env value of ${{ secrets.NAME }}A token cannot send a request that names a secret.Set the value in the pipeline definition, and start the pipeline with the token.
401 on a pipeline saveThe definition has secrets, or the token has no create or update on pipelines.Remove secrets from the definition, or use a person to save it.
401 on a start of a runThe token has update on pipelines but no run on pipeline-runs.Add run on pipeline-runs. No verb includes another.
429 with runs in flightThe token has its limit of live capsules and runs.Wait for a run to end, or raise max_live_runs in a new token.
403 with this token acts only for and a teamThe request names a team other than the reach of the token.Use the team of the token, or a token with the reach *.
400 with give the token a nameThe name is empty.Set name.
404 with no such ownerThe owner is not you or a team where you are an active member.Use your handle or one of your teams.
409 with pick a username before making a tokenThe account has no handle.Choose a handle in the console.
409 with tokens: and in useYou have 25 live tokens for that owner.Revoke one, then create the new token.
422 with is not a verb ofThe grant names a verb that its kind does not admit.Use a verb from the table of kinds.
422 with grants[0].resourcesA selector is not a name, an id, or a prefix that ends in *.Use letters, digits, -, _, ., /, and one final *.
422 with max_live_runsThe value is out of range, or the token has no run or exec verb.Use 1 to 50, and add a grant with run or exec.
A list is shorter than expectedThe grants select only some items, or own hides the rest.Follow x-next-cursor, or widen the selectors.
The secret is lostThe platform shows it once.Revoke the token and create a new one.