Account & Extensions
Account
Base path: /api/account — every route requires an authenticated session.
API keys are rejected here with
403 {"message":"This endpoint requires a signed-in session, not an API key"},
so a key can never change the account's credentials or mint further keys.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/account/session | Identify the signed-in user |
| GET | /api/account/profile | Read first/last name |
| POST | /api/account/updateprofile | Update first/last name |
| POST | /api/account/updatepassword | Change the password |
| GET | /api/account/api-keys | List active API keys |
| POST | /api/account/api-keys | Create an API key |
| PUT | /api/account/api-keys/:id | Rename a key or change its scopes |
| DELETE | /api/account/api-keys/:id | Revoke a key |
GET /api/me sits outside this router and accepts either a session or an API
key — see Who am I.
Session
GET /api/account/session
{
"id": "8a3f1c2d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"email": "me@example.com",
"thirdParty": { "id": "google", "userId": "1078…" }
}
id is the user id used throughout the API (workspace owner, users[].user,
audit log user). thirdParty is absent for email/password accounts. Use this
endpoint as a lightweight "am I signed in?" check — it returns 401 unauthorized
when there is no valid session.
Profile
GET /api/account/profile
POST /api/account/updateprofile
{ "success": true, "firstName": "Jane", "lastName": "Doe" }
Password
POST /api/account/updatepassword
{ "oldPassword": "current-secret", "newPassword": "new-secret" }
The old password is verified by re-authenticating the account. Failures:
| Status | Body |
|---|---|
400 | {"errors":[{"msg":"Invalid old password"}]} |
400 | {"errors":[{"msg":"Password must contain at least 8 characters, including a number"}]} |
200 | {"success":true} |
This route only applies to email/password accounts. Users who signed up through Google have no password to change.
API keys
GET /api/account/api-keys
POST /api/account/api-keys
PUT /api/account/api-keys/:id
DELETE /api/account/api-keys/:id
An API key authenticates as the user that created it. See Authentication for how to send one.
The key object
{
"id": "664e1a2b8c9d0e0011229b01",
"name": "CI deploy",
"prefix": "abk_1a2B3c4D",
"scopes": ["read", "write"],
"expiresAt": "2027-02-14T09:12:00.000Z",
"lastUsedAt": "2026-08-17T06:30:11.000Z",
"createdAt": "2026-02-14T09:12:00.000Z"
}
prefix is the non-secret head of the key — enough to tell two keys apart in a
list. The secret itself is never returned again after creation. expiresAt and
lastUsedAt are null when the key never expires or has not been used yet;
lastUsedAt is refreshed at most once a minute.
Listing
GET /api/account/api-keys returns { "keys": [ … ] }, newest first. Revoked
keys are omitted.
Creating
{ "name": "CI deploy", "scopes": ["read", "write"], "expiresInDays": 365 }
| Field | Required | Notes |
|---|---|---|
name | yes | 1–100 characters |
scopes | no | Non-empty array of read / write; defaults to ["read"]. read is always included. |
expiresInDays | no | Integer 1–365. Omit, or send null/0, for a key that never expires. |
The response is 201 with the key object plus the secret:
{
"id": "664e1a2b8c9d0e0011229b01",
"name": "CI deploy",
"prefix": "abk_1a2B3c4D",
"scopes": ["read", "write"],
"expiresAt": "2027-02-14T09:12:00.000Z",
"lastUsedAt": null,
"createdAt": "2026-02-14T09:12:00.000Z",
"key": "abk_1a2B3c4D5e6F…"
}
key is returned exactly once. Only its SHA-256 hash is stored, so a lost secret
cannot be recovered — revoke the key and create a new one.
Failures are 400 {"message":"…"}: a missing or over-long name, an unknown
scope, an out-of-range expiry, or already holding the maximum of 25 active keys.
Updating
{ "name": "CI deploy (staging)", "scopes": ["read"] }
Send either field or both; the secret never changes. An empty body returns
400 {"message":"Nothing to update"}, and an unknown or already-revoked id
returns 404 {"message":"API key not found"}. The updated key object is
returned.
Revoking
DELETE /api/account/api-keys/:id returns {"message":"API key revoked"} and
takes effect immediately. Revoked keys are retained internally rather than
deleted, so a revoked secret can never be reissued.
Who am I
GET /api/me
Accepts either a session cookie or an API key — the cheapest way for a client to verify its credentials.
{
"id": "8a3f1c2d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"email": "me@example.com",
"auth": "apiKey",
"apiKey": { "id": "664e1a2b8c9d0e0011229b01", "name": "CI deploy", "scopes": ["read", "write"] }
}
auth is apiKey or session; apiKey is null for session-authenticated
requests.
Extensions
Base path: /cgg/extensions. An extension is a marketplace package of features
that other users can add to their projects.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /cgg/extensions | optional | List extensions |
| GET | /cgg/extensions/:id | optional | Get one extension |
| POST | /cgg/extensions | session | Submit a new extension |
| PUT | /cgg/extensions/:id | session | Update your extension |
| PUT | /cgg/extensions/:id/status | session | Change the review status |
| DELETE | /cgg/extensions/:id | session | Delete your extension |
The extension object
{
"_id": "664e1a2b8c9d0e0011229b01",
"name": "Modbus RTU master",
"summary": "Poll Modbus RTU slaves over RS-485",
"description": "Long markdown description…",
"version": "1.2.0",
"author": "me@example.com",
"user": "8a3f…",
"status": "approved",
"features": [ /* feature definitions */ ],
"createdAt": "…",
"updatedAt": "…"
}
status is pending, approved or rejected.
Listing and reading
GET /cgg/extensions
GET /cgg/extensions/:id
| Query parameter | Description |
|---|---|
start | Offset, default 0 |
count | Page size, default 20 |
search | Case-insensitive match on name |
status | Return only extensions in this status |
Without an explicit status, a signed-in caller sees all approved extensions
plus their own pending ones; an anonymous caller sees only approved
extensions. Response uses the { count, data } envelope.
Reading a single extension that is not approved succeeds only for its author;
everyone else gets 404 {"error":"Extension not found"} — the same answer as for
a non-existent id.
Submitting and updating
POST /cgg/extensions
PUT /cgg/extensions/:id
{
"name": "Modbus RTU master",
"summary": "Poll Modbus RTU slaves over RS-485",
"description": "Long markdown description…",
"version": "1.2.0",
"features": [ /* feature definitions */ ]
}
New submissions always start as pending, with version defaulting to 1.0.0
and author set from the caller's email. PUT applies only the fields present in
the body and works only on extensions you own — someone else's id returns 404.
Both return the saved extension.
Review status
PUT /cgg/extensions/:id/status
{ "status": "approved" }
status must be pending, approved or rejected (400 otherwise).
Moderation is restricted: only users listed in the server's
EXTENSION_MODERATORS environment variable may set approved or rejected.
Authors may set their own extension back to pending to resubmit it after
changes. Anything else returns 403 {"error":"Insufficient permissions"}.
Deleting
DELETE /cgg/extensions/:id
Only the author may delete; returns 200 with an empty body.