Authenticate against the Light Cloud API - creating and managing API keys, what a key can and cannot do, and the session tokens the console and MCP server use.
Authentication
Every endpoint except health needs a credential. There are two, for two situations:
| Use it for | Available on | |
|---|---|---|
| API key | Scripts, CI, servers - anything running with nobody watching | Paid plans |
| Session token | The MCP server, the CLI, the console - anything with a person present (a browser, or a phone to approve a device code on) | Every plan, including free |
If a browser can open, you do not need a key. If one cannot, a key is the only way in.
Using a key#
Authorization: Bearer lc_...
No refreshing and no expiry to handle unless you set one. A key belongs to one organisation, so targetOrganisationId is optional on every call - it is inferred, and naming a different organisation is refused with CA003.
curl -X POST https://api.light-cloud.com/api/applications \
-H "Authorization: Bearer $LIGHT_CLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 100}'What a key cannot do#
By design, not by configuration:
- Billing and organisation settings. No key, at any access level, can change a payment method, invite a member, or delete a workspace. Those need a signed-in person.
- Manage keys. The endpoints below are session-only. A leaked key cannot mint a replacement or widen its own access - not even its own listing.
- Reach another workspace. A key is bound to the organisation it was created in.
Warning
A key is a live credential for the whole workspace. Put it in your CI secret store, never in a repository, and never in a client-side bundle where a browser can read it.
Plans#
Keys require a paid plan; creating one on a free plan returns 402. The entitlement is re-checked on every request, so a downgrade stops existing keys working rather than letting them outlive it.
This is a limit on unattended credentials, not on automation - the MCP server and the CLI work on every account and can do everything a key can, and more (a key can never touch billing). They just need a person to approve the initial sign-in, from any browser.
Create an API key#
/api/api-keys/createIssues a key and returns its secret. This is the only time the secret is ever readable - only a bcrypt hash is stored. Session tokens only.
- permission
- update:organisations
Parameters
targetOrganisationIdstringrequired- The organisation the key will belong to
namestringrequired- What will use it, e.g. "GitHub Actions"
rolestringdefault useruserfor read-only,adminto deploy and manage.owneris not assignableexpiresAtstringoptional- RFC 3339 timestamp in the future. Omit for a key that never expires
curl -X POST https://api.light-cloud.com/api/api-keys/create \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"targetOrganisationId": "org_1Ab",
"name": "GitHub Actions",
"role": "admin",
"expiresAt": "2027-08-28T00:00:00.000Z"
}'{
"id": "key_3Wq8",
"name": "GitHub Actions",
"prefix": "lc_RdiJfPH9X",
"role": "admin",
"expires_at": "2027-08-28T00:00:00.000Z",
"created_at": "2026-08-28T11:16:35.617Z",
"secret": "lc_RdiJfPH9XmS2cA7vK1pQ0tZbN4eL8yG6uW3rD5oX9jH"
}Important
secret appears in this response and nowhere else. If it is lost, revoke the key and create another.
List API keys#
/api/api-keysEvery key in the organisation, newest first. Never returns the secret or its hash - prefix is the display form, enough to tell keys apart and useless on its own. Session tokens only.
- permission
- read:organisations
Parameters
targetOrganisationIdstringrequired- The organisation to read
curl -X POST https://api.light-cloud.com/api/api-keys \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"targetOrganisationId": "org_1Ab"}'[
{
"id": "key_3Wq8",
"name": "GitHub Actions",
"prefix": "lc_RdiJfPH9X",
"role": "admin",
"created_by": "usr_5Kd",
"expires_at": null,
"revoked_at": null,
"last_used_at": "2026-08-28T09:41:02.000Z",
"created_at": "2026-08-20T10:00:00.000Z"
}
]Revoke an API key#
/api/api-keys/revokeStops a key working immediately - the next request with it is rejected. The row is kept rather than deleted, so the record of what existed survives. Session tokens only.
- permission
- update:organisations
Parameters
targetOrganisationIdstringrequired- The organisation the key belongs to
keyIdstringrequired- The key to revoke
curl -X POST https://api.light-cloud.com/api/api-keys/revoke \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"targetOrganisationId": "org_1Ab", "keyId": "key_3Wq8"}'{ "revoked": true }Revoke a key when the person who created it leaves, when the job it served is retired, or the moment you suspect it has leaked. A key without an expiry does not stop working on its own.
Tip
Set an expiry on anything temporary. A key that outlives its purpose is the one that turns up in an old pipeline two years later.
You can also do all of this in the console: left sidebar → your workspace → API keys.
Session tokens#
The console, the MCP server and the CLI use short-lived tokens rather than keys: an access token valid for 15 minutes, plus a refresh token valid for 30 days. Sessions started from a terminal (the device-code flow) are listed under Settings → Security → Connected devices, where they can be revoked.
Obtaining one happens in the browser, through the console. The MCP server does this for you; a person confirms the connection, and the tokens are written to ~/.lightcloud/credentials.json. Implementing that flow yourself is not a supported integration path - use an API key.
Note
POST /api/auth/login exists for the console. It expects the password encrypted with a platform secret that is not distributed, so it is not an authentication path for API clients.
Refresh a session token#
/api/auth/refreshExchanges a refresh token for a new pair. The presented token is revoked in the same call, so the value changes every time - which is why a refresh token cannot live in a CI secret, and why API keys exist.
Parameters
refreshTokenstringrequired- The current refresh token. Browsers may omit it and send the httpOnly cookie instead
curl -X POST https://api.light-cloud.com/api/auth/refresh \
-H "Content-Type: application/json" \
-H "X-Client-Type: cli" \
-d "{\"refreshToken\": \"$REFRESH_TOKEN\"}"{
"token": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "9c1f4e2a7b..."
}Important
X-Client-Type: cli is not optional outside a browser. Without it the response carries only a new access token, and your refresh token stays the old, now-revoked one.
Retrieve the current user#
/api/auth/profileThe signed-in user and every organisation they belong to, with their role in each. This is where targetOrganisationId comes from. Session tokens only - a key has no user behind it.
curl https://api.light-cloud.com/api/auth/profile \
-H "Authorization: Bearer $SESSION_TOKEN"{
"id": "usr_5Kd",
"email": "you@example.com",
"first_name": "Ada",
"organisations": [
{
"id": "org_1Ab",
"name": "Acme Labs",
"slug": "acme-labs",
"role": "owner"
}
]
}Sign out#
/api/auth/logoutRevokes the refresh token and clears the cookie. Access tokens already issued stay valid until they expire, up to 15 minutes later.
curl -X POST https://api.light-cloud.com/api/auth/logout \
-H "Authorization: Bearer $SESSION_TOKEN"{ "message": "Successfully logged out" }Two-factor accounts#
When an account has two-factor authentication enabled, signing in returns a short-lived pending token instead of a session, and a TOTP or recovery code exchanges it for the real one at POST /api/auth/login/mfa. The console and MCP server handle this.
API keys are unaffected - they are workspace credentials, not account credentials, and carry no second factor of their own. That is another reason they cannot touch billing or membership.