Skip to main content

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.

MethodPathPurpose
GET/api/account/sessionIdentify the signed-in user
GET/api/account/profileRead first/last name
POST/api/account/updateprofileUpdate first/last name
POST/api/account/updatepasswordChange the password
GET/api/account/api-keysList active API keys
POST/api/account/api-keysCreate an API key
PUT/api/account/api-keys/:idRename a key or change its scopes
DELETE/api/account/api-keys/:idRevoke 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:

StatusBody
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 }
FieldRequiredNotes
nameyes1–100 characters
scopesnoNon-empty array of read / write; defaults to ["read"]. read is always included.
expiresInDaysnoInteger 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…"
}
caution

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.

MethodPathAuthPurpose
GET/cgg/extensionsoptionalList extensions
GET/cgg/extensions/:idoptionalGet one extension
POST/cgg/extensionssessionSubmit a new extension
PUT/cgg/extensions/:idsessionUpdate your extension
PUT/cgg/extensions/:id/statussessionChange the review status
DELETE/cgg/extensions/:idsessionDelete 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 parameterDescription
startOffset, default 0
countPage size, default 20
searchCase-insensitive match on name
statusReturn 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.