Melso Docs

Overview and authentication

Verified request, response and authorization contracts.

The public REST base is https://melso.ai, verified against defaultCloudServerURL in server/cmd/melso/cmd_api.go. Self-hosted installations use their configured server origin. Paths below already include /api.

Send a credential as a bearer token. A workspace API key implies its workspace, so don't send X-Workspace-ID with one; if you do, it must name the key's workspace, or the request returns 403 with "code":"workspace_key_scope". A personal access token needs X-Workspace-ID, because a person can belong to several workspaces; the header selects an authorized workspace and does not grant membership. Workspace URLs also enforce membership in the workspace named in the path.

export MELSO_URL=https://melso.ai
export MELSO_TOKEN='mwk_…'   # a workspace API key, or a personal access token (mel_…)
export WORKSPACE_ID='your-workspace-uuid'   # needed with a personal access token; a key implies its own

Credential types

Workspace API keys (mwk_…) are recommended for integrations. Owners and admins create them in Settings → API keys. A key belongs to one workspace, not to a person: it authenticates as its own integration identity, a member of exactly that workspace, named after the key. Tasks a key creates are attributed to the key, and their Runs execute as its identity: its own harness funding policy, no member's subscription account, no long-term memories and no connected apps.

A key has a scope, chosen when it is created and fixed for its life; to change it, create a new key. Every key reaches the Task and Run APIs (including the Task event stream and the realtime WebSocket), file upload and attachment download, harness models, Cloud capabilities, GET /api/me (the key's identity, api_key_scope, and its workspace: id, name, slug), its own workspace, Areas (read-only), connected apps (always empty) and its harness policy. That is all a tasks key (the default) reaches. An admin key also reaches the workspace administration a platform integration needs, and on these routes only its identity acts as a workspace admin:

  • the member list, GET /api/workspaces/{id}/members (people only; key identities are never listed);
  • GitHub installations and their repositories, GET /api/workspaces/{id}/github/installations and GET /api/workspaces/{id}/github/installations/{installationId}/repositories;
  • the private network, GET, PUT and DELETE /api/workspaces/{id}/private-network;
  • Areas and their resources, POST /api/areas, PUT /api/areas/{id}, PUT /api/areas/{id}/instructions, GET and POST /api/areas/{id}/resources, and DELETE /api/areas/{id}/resources/{resourceId};
  • pull requests, GET /api/tasks/{id}/pull-requests, GET /api/pull-requests/resolve, GET /api/pull-requests/{id} and POST /api/pull-requests/{id}/merge;
  • repository variables, GET and PUT /api/secrets/repository;
  • the key's own provider accounts, which it never has: listing returns none, and starting a connection returns 403 provider_account_workspace_key.

An admin key is never an owner, and the Runs it starts keep member authority. Every other route, including API key, member, role, invitation, billing, vault and automation management, workspace settings and deletion, and the MCP endpoint, returns 403 workspace_key_scope to a key of either scope, as does an admin route to a tasks key. Revoking a key stops new API calls immediately and deletes the provider API keys saved in its funding policy; Runs already executing finish.

A personal access token (mel_…) still works and acts as its member in every workspace that member belongs to. Create one in Profile → API tokens. Browser cookies are also accepted. Run tokens (mrt_…) are execution-scoped and cannot use human-only routes. A Run acts as its execution owner: it can start, resume, rerun and message other Tasks in its workspace through the API or CLI, limited to 30 such actions per Run per hour, and a Run of an isolated Task stays confined to that Task. MCP OAuth access tokens authenticate the MCP endpoint, not arbitrary REST calls. Human-only handlers also reject MCP actors when reached through internal tool dispatch.

A Task is a durable conversation; a Run is an execution attempt. Store the Task ID and use webhooks to observe outcomes across follow-ups and automatic retries. Delivery means ready for review, not Task completion.

See Provider accounts and API keys for execution credentials. Those are separate from REST authentication.

Errors

JSON errors use {"error":"message"}; selected errors also include code and contextual fields. Check HTTP status before decoding success. 401 means authentication failed; 403 means the actor lacks permission; 404 may conceal an inaccessible resource; 400 rejects malformed input; 409 signals conflicting state; 429 indicates rate limiting; 500 is an internal error; 503 indicates an unavailable dependency. OAuth uses error and error_description.

Area, resource, Automation and Metric definition writes also require company-definition write permission: a workspace owner or admin, or an admin workspace API key on the Area routes it reaches. They commit and activate definitions through the company repository. A 409 containing operation means the definition was saved but is not active. Do not interpret it as successful activation. Deletes on these definition routes return a JSON commit receipt, not 204.

Rate limits

Requests made with a workspace API key are limited by default to 600 per minute per key, shared across every route the key reaches. Each key has its own budget. Over the limit, requests return 429 {"error":"too many requests"} with a Retry-After header in seconds; wait that long before retrying. Opening a Task event stream counts as one request, however long it stays open; so does each reconnect.

Idempotency

POST /api/tasks accepts client_request_id (UUID). Reuse it only with the same normalized creation payload and creator/workspace. A matching replay returns 201 with the original Task in its current state and the run_id of the Run the original request started, if it started one. The replay is answered before callback, execution and capability checks run, so once the Task exists a retry after a lost response cannot be refused with errors such as cloud_start_limited or model_incompatible. Identical requests sent concurrently create one Task and one Run. Changed input returns 409 with "code":"client_request_conflict". Do not substitute a Run ID.

POST /api/tasks/{id}/input accepts client_request_id (UUID) with the same contract, scoped to that Task and to the caller that sent it: a member, workspace API key or Run. A retry with the same content and attachment_ids returns 201 with the original message_id, run_id, queued, attachment_ids and created_at, answered before the Task’s current state (cancellation, execution settings, Cloud admission) is checked; your access to the Task is still checked first. Identical requests sent concurrently create one message and one Run. A different payload under the same ID returns 409 with "code":"client_request_conflict". The same ID from another caller, or on another Task, is a separate request. See Messages and interactions.

Other endpoints do not inherit this contract. Company-definition writes accept their own optional idempotency_key string.

Pagination

There is no universal pagination envelope. Tasks use {tasks,total} and limit/offset; messages have a separate cursor page route; many lists return arrays; Library lists return {files}. Each endpoint below states its shape. Never assume total is a global count: some handlers report only the returned slice length.

This reference covers customer integration surfaces. Daemon callbacks, internal worker control, billing and platform administration are intentionally outside its scope. Sources are the router and named handlers in this revision; no nonexistent REST endpoint is inferred from a similarly named MCP tool.

Examples use the environment variables from Overview. UUID placeholders must be replaced with your own IDs.

GET /api/tokens

Auth: Authenticated member; account-scoped.

Request: None.

Response: PersonalAccessTokenResponse array.

Status: 200. Authentication and resource-access errors follow Overview.

curl -sS -X GET "$MELSO_URL/api/tokens" \
  -H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"

POST /api/tokens

Auth: Authenticated member; account-scoped.

Request: name:string, expires_in_days?:integer; a positive value sets expiry, otherwise the token has no expiry.

Response: PersonalAccessTokenResponse plus one-time token:string.

Status: 201; 400 missing name or invalid JSON. Authentication and resource-access errors follow Overview.

curl -sS -X POST "$MELSO_URL/api/tokens" \
  -H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID" \
  -H "Content-Type: application/json" -d '{"name":"Factory","expires_in_days":90}'

DELETE /api/tokens/{id}

Auth: Token-owning member.

Request: Path: token UUID.

Response: Empty.

Status: 204. Authentication and resource-access errors follow Overview.

curl -sS -X DELETE "$MELSO_URL/api/tokens/$ID" \
  -H "Authorization: Bearer $MELSO_TOKEN" -H "X-Workspace-ID: $WORKSPACE_ID"

PersonalAccessTokenResponse

Source: server/internal/handler/personal_access_token.go (PersonalAccessTokenResponse). Referenced nested objects retain their handler-defined fields; clients should tolerate additional fields.

FieldJSON type
idstring
namestring
token_prefixstring
expires_atstring or null
last_used_atstring or null
created_atstring

GET /api/workspaces/{id}/api-keys

Auth: Workspace owner or admin with a browser session or personal access token. Run tokens, MCP credentials and workspace API keys receive 403.

Request: Path: workspace UUID.

Response: {keys: WorkspaceAPIKeyResponse[]} with every key that is not revoked, newest first. Expired keys stay listed until revoked.

Status: 200. Authentication and resource-access errors follow Overview.

curl -sS "$MELSO_URL/api/workspaces/$WORKSPACE_ID/api-keys" \
  -H "Authorization: Bearer $MELSO_TOKEN"

POST /api/workspaces/{id}/api-keys

Auth: As for listing keys. An owner or admin may create a key of either scope: an admin key reaches only routes a workspace admin can call.

Request: name:string (1–120 characters), expires_in_days?:integer from 1 to 3650 (omit it for a key that never expires), scope?:"tasks"|"admin" (default tasks; see Credential types). The scope never changes.

Response: {key: WorkspaceAPIKeyResponse, token: string}. token (mwk_…) is returned only here; Melso stores its hash.

Status: 201; 400 invalid name, expiry, scope or JSON; 429 when key creation is rate limited. Authentication and resource-access errors follow Overview.

curl -sS -X POST "$MELSO_URL/api/workspaces/$WORKSPACE_ID/api-keys" \
  -H "Authorization: Bearer $MELSO_TOKEN" \
  -H "Content-Type: application/json" -d '{"name":"Production","expires_in_days":90}'
curl -sS -X POST "$MELSO_URL/api/workspaces/$WORKSPACE_ID/api-keys" \
  -H "Authorization: Bearer $MELSO_TOKEN" \
  -H "Content-Type: application/json" -d '{"name":"Platform integration","scope":"admin"}'

DELETE /api/workspaces/{id}/api-keys/{keyId}

Auth: As for listing keys.

Request: Path: workspace UUID and key UUID.

Response: Empty. The key stops working on its next request; revoking a revoked or unknown key is a no-op. Provider API keys saved in its funding policy are deleted; the rest of that policy stays. Its Tasks keep their attribution, and Runs already executing finish.

Status: 204. Authentication and resource-access errors follow Overview.

curl -sS -X DELETE "$MELSO_URL/api/workspaces/$WORKSPACE_ID/api-keys/$KEY_ID" \
  -H "Authorization: Bearer $MELSO_TOKEN"

WorkspaceAPIKeyResponse

Source: server/internal/handler/workspace_api_key.go (WorkspaceAPIKeyResponse). Clients should tolerate additional fields.

FieldJSON type
idstring
namestring
token_prefixstring
scopestring: tasks or admin
created_bystring
created_atstring
last_used_atstring or null
expires_atstring or null

Rotate a workspace integration key

A human workspace owner/admin can call POST /api/workspaces/{id}/api-keys/{keyId}/rotate. It returns {key,token} once with Cache-Control: no-store. The old token stops authenticating immediately; existing streams recheck their credential within a minute. Rotation preserves the key ID, integration identity, scope, expiry and all managed execution customers. Expired or revoked keys return 404 and cannot be revived through rotation. Workspace keys, Run credentials and MCP grants cannot rotate keys. Store the new token securely in the integration backend.