Authentication
Create, use, scope and revoke API keys, and how they differ from CLI and dashboard tokens.
Every request under /api/v1 carries a bearer token:
Authorization: Bearer <token>There is no cookie or session auth on the API, and no unauthenticated write path.
Token types
| Token | Format | Lifetime | Created by |
|---|---|---|---|
| API key | sk- + 64 hex chars | Until revoked, or an optional expiry | You, per workspace |
| CLI session token | Hex string, no dots | Until revoked or expired | smll login |
| Dashboard JWT | Three dot-separated parts | Short, auto-refreshed | Supabase Auth |
The API tells them apart by shape: an sk- prefix means an API key, a token containing dots is parsed as a JWT, and anything else is looked up as a CLI session. All three resolve to the same thing (a user), so any endpoint accepts any of them.
For scripts, CI and integrations, use an API key. The other two exist for interactive use and are tied to a login.
Create an API key
From the dashboard: Settings > API Keys > Create key.
From the CLI:
smll api-keys create --name "GitHub Actions" --expires 90dOver the API, creating a key needs the Owner or Admin role:
curl -s -X POST \
-H "Authorization: Bearer $EXISTING_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"GitHub Actions","expires_in_days":90}' \
https://api.smll.io/api/v1/workspaces/$WORKSPACE_ID/api-keys| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | Must be unique in the workspace, else 409 |
expires_in_days | integer | No | Omit for a key that never expires |
scopes | string[] | No | Recorded on the key, see Scopes below |
The response is the key record plus the secret itself:
{
"id": "b4c1...",
"workspace_id": "acaa...",
"name": "GitHub Actions",
"key_prefix": "sk-3f9c1a2b4",
"scopes": [],
"expires_at": "2026-11-18T12:00:00Z",
"created_at": "2026-08-20T12:00:00Z",
"key": "sk-3f9c1a2b4..."
}key is returned once, at creation. Only a SHA-256 hash is stored, so a lost key cannot be recovered or re-displayed. Create a replacement and revoke the old one.
Afterwards, listing keys shows key_prefix (the first 12 characters) so you can tell them apart, never the full value.
Use a key
curl -s -H "Authorization: Bearer sk-..." \
https://api.smll.io/api/v1/workspaces/$WORKSPACE_ID/vpcsEach accepted request updates the key's last_used_at, which the dashboard shows. That is the quickest way to find keys nothing uses any more.
What a key can do
A key acts as the user who created it, and inherits that user's role in the workspace at the time each request is made:
- A key created by an Owner or Admin can create, update and delete.
- A key created by a Developer or Viewer is read-only, and gets
403 insufficient workspace permissionson writes. - If the user's role is later downgraded, the key loses that access too. If the user is removed from the workspace, the key stops working there.
A key is not confined to the workspace it was created in. It authenticates the user, so it reaches every workspace that user belongs to, with whatever role they hold in each. Treat a key as equivalent to that person's account access, and give CI its own key on a least-privileged user rather than sharing one.
Scopes
The scopes field is accepted and stored on the key, and shows up when you list keys, but it does not currently restrict anything. Do not rely on it as a security boundary. Until scope enforcement ships, limit a key's reach by choosing the role of the user that creates it.
Expiry and revocation
A key stops working the moment it is revoked, and at the instant expires_at passes. Either way the next request gets:
{ "error": "invalid or expired API key" }with status 401. There is no grace period and no warning email.
Revoke from the dashboard, with smll api-keys revoke <id>, or:
curl -s -X DELETE \
-H "Authorization: Bearer $EXISTING_TOKEN" \
https://api.smll.io/api/v1/workspaces/$WORKSPACE_ID/api-keys/$KEY_IDA successful revoke returns 204 with no body. Revocation is permanent: keys are not un-revoked, and the same secret is never reissued.
Rotating a key
Because the secret is only shown at creation, rotation is create-then-revoke, in that order:
- Create the replacement key.
- Update the consumer (CI secret, deployment config) to the new value.
- Confirm the new key's
last_used_athas moved. - Revoke the old key.
Doing it the other way round means downtime between the revoke and the redeploy.
Endpoints
| Method | Path | Role |
|---|---|---|
GET | /api/v1/workspaces/{workspaceID}/api-keys | Admin |
POST | /api/v1/workspaces/{workspaceID}/api-keys | Admin |
DELETE | /api/v1/workspaces/{workspaceID}/api-keys/{keyID} | Admin |
Listing and creating keys both require Owner or Admin, so a Developer cannot enumerate a workspace's keys.
Using the CLI in CI
The CLI has no environment-variable login. To use it in a job, write its two config files yourself:
mkdir -p ~/.smll
jq -n --arg t "$SMLL_API_KEY" \
'{api_url:"https://api.smll.io", token:$t}' > ~/.smll/credentials.json
jq -n --arg w "$WORKSPACE_ID" --arg v "$VPC_ID" \
'{workspace_id:$w, vpc_id:$v}' > ~/.smll/config.json
chmod 600 ~/.smll/*.jsonFor most CI work, calling the API with curl is simpler. See Deploying from GitHub Actions for a complete workflow.