This document is a practical integration manual for AI coding tools, automation agents, desktop clients, control planes, and external programs that need to call MaClawSrv directly. The goal is to let integrators work efficiently without repeatedly reading the source, while making each endpoint's purpose, inputs, outputs, and usage patterns explicit.
MaClawSrv exposes Maclaw as a multi-tenant, multi-user REST service.
Key characteristics:
-
Data isolation is
tenant -> user. -
Multiple logical instances can run under the same user at the same time.
-
All instances of the same user reuse the same config, memory, skill set, and long-term data.
-
Core runtime behavior is reused from
corelib/agentserviceand shared agent infrastructure. -
MaClawSrvis a pure agent service. It does not expose coding-session orchestration APIs. -
Skill management and MCP management are also available through REST.
Default listen address:
http://127.0.0.1:18080
Health endpoint:
GET /health
Response:
{
"status": "ok"
}
Transport guidance:
-
By default, plain HTTP should only be used on loopback, and the default bind is
127.0.0.1:18080. -
For remote deployment, configure TLS with
MACLAW_TLS_CERT_FILEandMACLAW_TLS_KEY_FILE. -
Do not send admin secrets, API secrets, or bearer tokens over insecure remote HTTP.
-
Machine-readable API discovery is available at
GET /openapi.jsonandGET /api/v1/openapi.json. -
GET /readyzis a readiness probe and returns503when the configured data root is unavailable, not a directory, or not writable. -
GET /api/v1/admin/system/readinessis an admin-only diagnostic endpoint that returns the detailed readiness report, including path-level writable checks.
Required security variables:
-
MACLAW_ADMIN_SECRET: admin control-plane secret, at least 24 chars. -
MACLAW_TOKEN_SECRET: bearer token signing secret, at least 32 chars.
Common runtime variables:
-
MACLAW_HTTP_ADDR: listen address. Default127.0.0.1:18080. Wildcard addresses such as:18080are treated as non-loopback and require TLS orMACLAW_ALLOW_INSECURE_HTTP=true. -
MACLAW_DATA_ROOT: root data directory. Default~/.maclaw_srv. -
MACLAW_TLS_CERT_FILE: TLS cert path. -
MACLAW_TLS_KEY_FILE: TLS key path. -
MACLAW_ALLOW_INSECURE_HTTP: allows non-loopback HTTP when set totrue. -
MACLAW_CREDENTIAL_PEPPER: optional extra pepper for credential hashing.
Host-local bash related variables:
-
MACLAW_ENABLE_LOCAL_BASH -
MACLAW_LOCAL_BASH_TRUSTED_SINGLE_USER -
MACLAW_LOCAL_BASH_TENANT_ID -
MACLAW_LOCAL_BASH_USER_ID
Important:
-
Local bash is security-sensitive and should stay disabled in multi-tenant deployments unless an outer OS/container sandbox exists.
-
Admin PATCH tenant/user requests also support
max_instances,max_sessions,max_messages, andmax_runsquota fields. -
A value of
0means unlimited. When both tenant and user quotas are set, the stricter limit applies. -
Quota violations return
429 Too Many Requests.
MaClawSrv has two authentication layers: admin APIs and user runtime APIs.
Admin APIs use a static header:
X-MaClaw-Admin-Secret: <admin-secret>
These are used for tenants, users, credentials, audit events, and other control-plane operations.
User APIs use a bearer token.
Standard flow:
-
The admin side creates a tenant, user, and credential.
-
The client exchanges
api_key + api_secretfor a token. -
The client calls user-scoped APIs with
Authorization: Bearer <token>.
Token endpoint:
POST /api/v1/auth/token
Content-Type: application/json
Request body:
{
"api_key": "ak_xxx",
"api_secret": "secret_xxx"
}
Field notes:
-
api_key: public credential identifier. -
api_secret: secret value stored only by the client; the server does not return it later in plaintext.
Response example:
{
"access_token": "...",
"token_type": "Bearer",
"expires_at": "2026-04-23T20:30:00Z",
"principal": {
"tenant_id": "tenant_xxx",
"user_id": "user_xxx"
}
}
Response fields:
-
access_token: bearer token for subsequent requests. -
token_type: alwaysBearer. -
expires_at: expiration timestamp in RFC3339 format. -
principal.tenant_id: current tenant. -
principal.user_id: current user. -
Bearer tokens are bound to the issuing credential version, so secret rotation or credential revoke invalidates previously issued tokens immediately.
Current-user endpoint:
GET /api/v1/me
Authorization: Bearer <token>
This returns a User object. Typical fields include:
-
id -
tenant_id -
name -
email -
status -
created_at -
updated_at
For AI tools and desktop clients, use this sequence:
-
POST /api/v1/auth/token -
GET /api/v1/me -
GET /api/v1/config/schema -
GET /api/v1/config -
POST /api/v1/config/validate -
PUT /api/v1/configif missing fields must be filled -
POST /api/v1/config/testif the client wants a real connectivity check -
POST /api/v1/instances -
GET /api/v1/instances/{instanceId}/capabilities -
POST /api/v1/instances/{instanceId}/messages -
Poll
GET /api/v1/instances/{instanceId}/runs/{runId}or read message history
This separates configuration problems from runtime failures and makes it easier to surface setup issues in UI.
User-scoped request:
Authorization: Bearer <token>
Accept: application/json
Content-Type: application/json
Admin-scoped request:
X-MaClaw-Admin-Secret: <admin-secret>
Accept: application/json
Content-Type: application/json
Typical errors are JSON:
{
"error": "..."
}
Config-related failures may include extra details:
{
"error": "invalid config",
"config_validation": {
"valid": false,
"issues": [
{
"key": "maclaw_llm_key",
"message": "is required"
}
]
}
}
Recommended handling:
-
400: invalid request fields, incomplete config, or unmet business preconditions. -
401: authentication failure. -
404: missing tenant, user, instance, session, run, skill, or MCP server. -
409: state conflict, for example a cancelled run or writing to an archived session. -
429: throttled token/login flow; retry is appropriate. -
502: downstream executor or external dependency failure; the response may still include a createdrun. -
500: server-side failure.
Several list endpoints support cursor pagination.
Query parameters:
-
limit: default100, max500. -
before: endpoint-specific cursor value. Most list endpoints use an RFC3339 or RFC3339Nano timestamp.GET /api/v1/skillsuses the skillnamereturned bynext_before.
Typical response shape:
{
"items": [],
"limit": 100,
"has_more": true,
"next_before": "2026-04-23T18:00:00Z"
}
Field meanings:
-
items: current page items. -
limit: effective page size. -
has_more: whether another page exists. -
next_before: thebeforevalue to use for the next page.
Supported on:
-
GET /api/v1/admin/tenants -
GET /api/v1/admin/users -
GET /api/v1/admin/tenants/{tenantId}/users -
GET /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials -
GET /api/v1/instances -
GET /api/v1/instances/{instanceId}/sessions -
GET /api/v1/instances/{instanceId}/sessions/{sessionId}/messages -
GET /api/v1/instances/{instanceId}/runs -
GET /api/v1/admin/audit-events -
GET /api/v1/mcp/servers -
GET /api/v1/skills
These APIs are typically used by control planes, installers, provisioning tools, or admin backends.
POST /api/v1/admin/tenants
Request body:
{
"name": "Acme"
}
Field notes:
name: tenant display name.
Response: Tenant object.
Main fields:
-
id -
name -
status:activeordisabled -
created_at -
updated_at
GET /api/v1/admin/tenants
Supports filtering via status and name, plus pagination via limit and before.
Response:
{
"items": [
{
"id": "tenant_xxx",
"name": "Acme",
"status": "active"
}
]
}
GET /api/v1/admin/tenants/{tenantId}
PATCH /api/v1/admin/tenants/{tenantId}
GET /api/v1/admin/tenants/{tenantId}/users supports filtering via status, name, and email, plus pagination via limit and before.
Patch fields may include:
-
name -
status:activeordisabled
Example:
{
"status": "disabled"
}
GET /api/v1/admin/users
Supports filtering via tenant_id, status, name, and email, plus pagination via limit and before.
This endpoint is intended for operator consoles that need one global user search view without first selecting a tenant.
POST /api/v1/admin/tenants/{tenantId}/users
Request body:
{
"name": "Alice",
"email": "alice@example.com"
}
Field notes:
-
name: user name. -
email: optional contact or login mapping information.
Response: User object.
Main fields:
-
id -
tenant_id -
name -
email -
status -
created_at -
updated_at
GET /api/v1/admin/tenants/{tenantId}/users
GET /api/v1/admin/tenants/{tenantId}/users/{userId}
PATCH /api/v1/admin/tenants/{tenantId}/users/{userId}
GET /api/v1/admin/tenants/{tenantId}/users supports filtering via status, name, and email, plus pagination via limit and before.
Patch fields may include:
-
name -
email -
status:activeordisabled
POST /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials
Request body:
{
"name": "default-client",
"api_key": "ak_demo_client",
"api_secret": "super-secret"
}
Field notes:
-
name: credential label for admin-side identification. -
api_key: public identifier. -
api_secret: secret value stored by the client.
Response: Credential object.
Main fields:
-
id -
tenant_id -
user_id -
name -
api_key -
api_key_prefix -
status -
created_at -
updated_at
Notes:
-
api_secretis only known to the client at creation time. -
Bearer tokens are associated with the credential that issued them.
-
Rotating a credential secret, rotating its API key, suspending it, or revoking it invalidates already-issued bearer tokens from that credential immediately.
Credential status values are active, suspended, and revoked.
-
expires_atcan be set on credential update to make a credential stop working at a specific RFC3339 time. -
clear_expires_at=trueon credential update removes an existing expiration.
GET /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials
DELETE /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials/{credentialId}
GET /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials supports pagination via limit and before.
GET /api/v1/admin/system/readiness
GET /api/v1/admin/overview
GET /api/v1/admin/dashboard
GET /api/v1/admin/alerts
Recommended usage:
-
system/readiness: detailed admin-only readiness checks for the data root, state directory, and async-job persistence path. -
overview: whole-service counters for tenants, users, instances, sessions, messages, runs, and audit totals. -
dashboard: overview plus recent audit events and 24h/7d trend buckets for homepages and operator consoles. -
alerts: operational alert feed for unready instances, waiting runs, and failed runs.
GET /api/v1/admin/alerts supports these query parameters:
-
tenant_id -
user_id -
kind:unready_instance,waiting_run, orfailed_run -
since: RFC3339 or RFC3339Nano timestamp -
limit: limits normalized alertitems
GET /metrics returns Prometheus text format counters for tenants, users, instances, sessions, messages, runs, and audit events.
Response notes for alerts:
-
items: normalized alert list for admin dashboards and automation consumers. -
unready_instances: backward-compatible detailed instance list. -
waiting_runs: backward-compatible detailed waiting run list. -
failed_runs: backward-compatible detailed failed run list. -
generated_at: response generation timestamp.
Each items[] entry may include:
-
kind -
severity -
title -
suggested_action -
tenant_id -
user_id -
instance_id -
session_id -
run_id -
occurred_at -
reason
GET /api/v1/admin/audit-events
Optional filters:
-
tenant_id -
user_id -
action -
resource_type -
limit -
before
Response items are AuditEvent objects. Main fields include:
-
id -
tenant_id -
user_id -
actor_type -
actor_tenant_id -
actor_user_id -
action -
resource_type -
resource_id -
metadata -
created_at
GET /api/v1/admin/export
Query parameters:
-
tenant_id: optional, narrows export to one tenant. -
user_id: optional, requirestenant_id, narrows export to one user. -
include_messages: optional boolean, defaulttrue. -
include_runs: optional boolean, defaulttrue. -
include_audit: optional boolean, defaulttrue. -
include_secrets: optional boolean, defaultfalse.
Behavior:
-
Export scope is
service,tenant, oruserdepending on filters. -
Default export is safe-by-default: config secrets are masked and credential internals are sanitized.
-
Setting
include_secrets=truereturns unsanitized user config plus internal credential hash fields that are useful for backup or migration tooling. -
user_idwithouttenant_idreturns400.
Response includes:
-
tenants -
users[] -
users[].config -
users[].credentials -
users[].instances[].sessions[].messageswhen enabled -
users[].instances[].runswhen enabled -
audit_eventswhen enabled -
exported_at
POST /api/v1/admin/import
Supported request styles:
-
Raw
ExportServiceStateOutputJSON body. -
Wrapper body:
{ "data": <export>, "overwrite": true|false, "dry_run": true|false }.
Query parameters:
-
overwrite: optional boolean override for body-level overwrite behavior. -
dry_run: optional boolean precheck mode that validates and reports conflicts without writing state.
Behavior:
-
Imported instance
data_dir,runtime_dir, andworkspace_dirare remapped into the current service data root. -
Importing the same IDs twice returns
409 Conflictunlessoverwrite=true. -
dry_run=truereturns200with predicted counters plusconflicts,warnings, and a detailedplanarray describing per-resourcecreateoroverwriteactions, and does not mutate service state. -
For full credential restore, the export must be created with
include_secrets=truesosecret_digestis present. -
Safe display exports can still be imported for structural restore, but masked config secrets will stay masked and are not suitable for a fully working runtime.
Response includes aggregate counters for imported tenants, users, credentials, instances, sessions, messages, runs, and audit events. During precheck, it can also include conflicts, warnings, and plan entries such as tenant/user overwrite actions and nested resource create actions.
Each user has one shared config used by all instances under that user.
GET /api/v1/config/schema
Response:
{
"items": [
{
"key": "maclaw_llm_url",
"title": "LLM URL",
"description": "...",
"required": true,
"secret": false,
"type": "string",
"example": "https://api.openai.com/v1"
}
]
}
Field meanings:
-
key: config field name. -
title: display label. -
description: field explanation. -
required: whether the field is required. -
secret: whether the field is sensitive. -
type: field type, for examplestring. -
example: suggested example value.
Use this endpoint to build dynamic settings UI instead of hardcoding config fields.
GET /api/v1/config
Response example:
{
"tenant_id": "tenant_xxx",
"user_id": "user_xxx",
"app_config": {
"maclaw_llm_url": "https://api.openai.com/v1",
"maclaw_llm_model": "gpt-5.4"
},
"updated_at": "2026-04-23T18:00:00Z"
}
Field meanings:
-
tenant_id,user_id: owner of the config. -
app_config: effective Maclaw config. -
updated_at: last update time.
Notes:
-
Sensitive fields are sanitized in responses.
-
If no config has been saved yet, the server returns an empty
app_config.
PUT /api/v1/config
Request body accepts raw AppConfig JSON:
{
"maclaw_llm_url": "https://api.openai.com/v1",
"maclaw_llm_key": "sk-...",
"maclaw_llm_model": "gpt-5.4"
}
Notes:
-
PUT /configaccepts the raw config object, not anapp_configwrapper. -
Existing secret fields are preserved/merged server-side when appropriate.
Response: updated UserConfig.
POST /api/v1/config/validate
Supported request shapes:
-
Empty body: validate the saved config.
-
Raw
AppConfig: validate a candidate config. -
{"app_config": {...}}: also validates a candidate config.
Response example:
{
"valid": false,
"issues": [
{
"key": "maclaw_llm_model",
"message": "is required"
}
]
}
Field meanings:
-
valid: whether validation passed. -
issues[].key: invalid config key. -
issues[].message: user-facing explanation.
POST /api/v1/config/test
Request body format matches validate.
Response example:
{
"success": true,
"message": "ok",
"latency_ms": 812,
"endpoint": "https://api.openai.com/v1",
"provider_name": "openai",
"model": "gpt-5.4",
"protocol": "openai",
"wire_api": "chat_completions"
}
Field meanings:
-
success: whether connectivity succeeded. -
message: success message. -
error: failure message. -
latency_ms: request latency. -
endpoint: tested provider endpoint. -
provider_name: detected provider. -
model: tested model. -
protocol,wire_api: lower-level protocol identification. -
validation: optional validation result if config issues also exist.
An instance is a logical runtime entrypoint. It does not own separate long-term memory or config; it reuses shared user-level state.
GET /api/v1/instances
Supports filtering via status and name, plus pagination via limit and before.
Response items are Instance objects. Main fields include:
-
id -
tenant_id -
user_id -
name -
data_dir -
runtime_dir -
workspace_dir -
status -
ready -
ready_reason -
readiness -
description -
metadata -
config_validation -
created_at -
updated_at
Important semantics:
-
status: lifecycle state, typicallyreadyorstopped. -
ready: whether the instance is currently usable. -
ready_reason: direct reason when not usable. -
readiness.ready: structured readiness state. -
readiness.config_valid: whether config passed validation. -
readiness.has_llm_config: whether minimum LLM config exists. -
config_validation.issues: user-displayable config errors.
POST /api/v1/instances
Request body:
{
"name": "primary-agent",
"description": "default assistant entrypoint",
"metadata": {
"channel": "wails-demo"
}
}
Field notes:
-
name: instance name. -
description: instance description. -
metadata: application-defined labels preserved as-is.
Response: created Instance.
If config is incomplete, the endpoint may return 400 with config_validation details.
GET /api/v1/instances/{instanceId}
Fields typically worth surfacing prominently:
-
status -
ready -
ready_reason -
readiness -
config_validation -
data_dir -
runtime_dir -
workspace_dir
PATCH /api/v1/instances/{instanceId}
GET /api/v1/admin/tenants/{tenantId}/users supports filtering via status, name, and email, plus pagination via limit and before.
Patch fields may include:
-
name -
description -
metadata
This updates mutable instance metadata only; it does not change lifecycle state.
DELETE /api/v1/instances/{instanceId}
Response:
{
"status": "deleted"
}
GET /api/v1/instances/{instanceId}/capabilities
This is a key endpoint for AI clients and should usually be called immediately after selecting an instance.
Important policy notes:
-
supports_sshis onlytruewhen the deployment allows direct SSH or the user has configured SSH host labels. -
supports_local_bashis onlytruefor the explicitly scoped trusted single-user deployment. -
Clients should hide or disable SSH and bash affordances when the corresponding capability is
falseor the tool entry is disabled.
Main fields:
-
executor -
supports_sessions -
supports_ask_user -
supports_ssh -
supports_local_bash -
tools -
metadata
tools[] fields:
-
name -
description -
enabled -
disabled_reason -
parameters
parameters can be used directly to drive UI forms for tool invocation or capability display.
POST /api/v1/instances/{instanceId}/stop
POST /api/v1/instances/{instanceId}/resume
POST /api/v1/instances/{instanceId}/refresh-readiness
Response: latest Instance.
GET /api/v1/instances/{instanceId}/bootstrap
Returns InstanceBootstrap with fields such as:
-
instance_id -
tenant_id -
user_id -
data_dir -
runtime_dir -
workspace_dir -
config_path -
conversation_store_path -
confirmation_store_path -
metadata -
generated_at
POST /api/v1/instances/{instanceId}/messages
Request fields:
-
session_id: optional, continue an existing session. -
agent_id: optional, choose an agent ID when creating a session. -
title: optional, title for a new session. -
content: required user input. -
input_type: optional, for exampletext. -
metadata: optional message metadata. -
session_metadata: optional metadata for a new session. -
client_session_key: optional client-side idempotency/helper key. -
client_message_id: optional client-side message correlation ID.
Example:
{
"title": "Demo session",
"content": "Please summarize current project status and next steps.",
"client_message_id": "msg_local_001"
}
Success response example:
{
"session": {
"id": "sess_xxx"
},
"run": {
"id": "run_xxx",
"status": "succeeded"
},
"message": {
"id": "msg_xxx",
"role": "assistant",
"content": "..."
}
}
POST /api/v1/instances/{instanceId}/sessions
Request body:
{
"agent_id": "default",
"title": "Planning",
"metadata": {
"source": "external-tool"
}
}
GET /api/v1/instances/{instanceId}/sessions
Supported query parameters:
-
include_archived -
limit -
before
Response items are Session objects. Main fields include:
-
id -
tenant_id -
user_id -
instance_id -
agent_id -
title -
metadata -
archived -
archived_at -
waiting_for_user -
pending_ask -
last_message_at -
created_at -
updated_at
Important semantics:
-
waiting_for_user: whether the agent is waiting for a reply. -
pending_ask.question: prompt to present to the user. -
pending_ask.input_type: answer type. -
pending_ask.options: explicit answer choices when available.
GET /api/v1/instances/{instanceId}/sessions/{sessionId}
PATCH /api/v1/instances/{instanceId}/sessions/{sessionId}
DELETE /api/v1/instances/{instanceId}/sessions/{sessionId}
GET /api/v1/admin/tenants/{tenantId}/users supports filtering via status, name, and email, plus pagination via limit and before.
Patch fields may include:
-
title -
metadata
Deleting a session typically also removes associated messages and runs, so clients should usually confirm before calling it.
POST /api/v1/instances/{instanceId}/sessions/{sessionId}/archive
POST /api/v1/instances/{instanceId}/sessions/{sessionId}/restore
Behavior notes:
-
Archived sessions are hidden from default listings.
-
Posting into an archived session usually returns
409. -
Use
include_archived=truewhen showing historical conversations.
GET /api/v1/instances/{instanceId}/sessions/{sessionId}/messages
Supports filtering via status and name, plus pagination via limit and before.
Response items are Message objects. Main fields include:
-
id -
session_id -
tenant_id -
user_id -
instance_id -
role:user,assistant, orsystem -
input_type -
output_type -
content -
metadata -
created_at
For transcript rendering, this endpoint should be treated as the source of truth.
POST /api/v1/instances/{instanceId}/sessions/{sessionId}/messages
Request body:
{
"content": "Continue from the previous result.",
"input_type": "text",
"metadata": {
"source": "external-tool"
}
}
Success response:
{
"run": {
"id": "run_xxx"
},
"message": {
"id": "msg_xxx",
"role": "assistant",
"content": "..."
}
}
GET /api/v1/instances/{instanceId}/runs
Supported query parameters:
-
status:running,succeeded,failed,cancelled -
session_id -
response_source: currentlyask_userwhen filtering runs waiting on structured user input -
waiting_for_user:trueorfalse -
limit -
before
Response items are Run objects. Main fields include:
-
id -
tenant_id -
user_id -
instance_id -
session_id -
user_message_id -
assistant_message_id -
status -
error -
response_source -
waiting_for_user -
duration_ms -
started_at -
completed_at -
metadata
GET /api/v1/instances/{instanceId}/runs/{runId}
This is the simplest polling endpoint for run status. When assistant_message_id appears, clients can fetch the message list for the full assistant content.
GET /api/v1/instances/{instanceId}/runs/{runId}/events
Content type: text/event-stream.
Event types:
-
snapshot -
done -
error
Payload example:
{
"type": "snapshot",
"snapshot": {
"run": {
"id": "run_xxx",
"status": "running"
},
"session": {
"id": "sess_xxx"
},
"assistant_message": {
"id": "msg_xxx",
"content": "partial or final content"
}
}
}
Use SSE when the client wants more real-time updates than polling.
POST /api/v1/instances/{instanceId}/runs/{runId}/cancel
Response: cancelled Run object. The run status should become cancelled.
GET /api/v1/usage/summary
Useful for dashboards and account-overview pages.
Response fields:
-
tenant_id -
user_id -
data_dir -
instances -
ready_instances -
stopped_instances -
sessions -
messages -
user_messages -
assistant_messages -
runs -
runs_by_status -
last_activity_at
Skills are user-scoped shared resources. All instances of the same user share the same installed skills.
GET /api/v1/skills
Supports filtering via status and name, plus pagination via limit and before.
For this endpoint, before is a case-insensitive skill-name cursor rather than a timestamp.
POST /api/v1/skills/search
Request body:
{
"query": "ssh deploy",
"sources": ["github", "skillmarket"],
"top_n": 10,
"include_installed": true,
"skill_hub_url": "https://skillhub.example.com",
"skill_market_url": "https://market.example.com",
"github_token": "ghp_xxx"
}
Add ?async=true to return 202 Accepted and a job resource instead of waiting for completion.
POST /api/v1/skills/install
GitHub example:
{
"source": "github",
"repo_full_name": "owner/repo",
"file_path": "skills/my-skill/SKILL.md",
"branch": "main",
"overwrite": true,
"github_token": "ghp_xxx"
}
SkillHub example:
{
"source": "skillhub",
"skill_id": "skill_xxx",
"skill_hub_url": "https://skillhub.example.com"
}
Zip example:
{
"source": "zip",
"zip_base64": "<base64-zip>",
"overwrite": true
}
Add ?async=true to start an async import job and poll later.
POST /api/v1/skills/import
Request body:
{
"zip_base64": "<base64-zip>",
"overwrite": true,
"archive_name": "demo-skill.zip"
}
GET /api/v1/skills/{skillName}
DELETE /api/v1/skills/{skillName}
GET /api/v1/skills/{skillName}/export
POST /api/v1/skills/{skillName}/validate
POST /api/v1/skills/{skillName}/improve
improve request body:
{
"auto_fix": true
}
Add ?async=true to start upload in the background and poll the returned job.
POST /api/v1/skills/{skillName}/upload
Request body:
{
"skill_market_url": "https://market.example.com",
"email": "author@example.com"
}
GET /api/v1/jobs/{jobId}
Example response:
{
"id": "job_xxx",
"kind": "skill.import",
"status": "succeeded",
"tenant_id": "tenant_xxx",
"user_id": "user_xxx",
"result": {
"items": [
{ "name": "demo-skill" }
]
},
"created_at": "2026-04-26T10:00:00Z",
"started_at": "2026-04-26T10:00:00Z",
"completed_at": "2026-04-26T10:00:01Z"
}
GET /api/v1/jobs?kind=skill.import&status=succeeded
DELETE /api/v1/jobs?status=succeeded&before=2026-04-26T10:05:00Z
POST /api/v1/jobs/{jobId}/cancel
DELETE /api/v1/jobs/{jobId}
Use GET /api/v1/jobs?kind=skill.import&status=succeeded for recent user-scoped jobs, DELETE /api/v1/jobs?status=succeeded&before=<timestamp> or DELETE /api/v1/jobs?all=true for bulk cleanup of finished jobs, POST /api/v1/jobs/{jobId}/cancel to request cancellation for a pending or running job, and DELETE /api/v1/jobs/{jobId} to remove a single finished job record.
status can be pending, running, succeeded, failed, or canceled. Bulk deletion only accepts terminal statuses.
Check async upload status:
GET /api/v1/skill-uploads/{submissionId}
Get market account profile:
GET /api/v1/skill-market/account?email=author@example.com&base_url=https://market.example.com
Async MCP actions are also supported for heavier operations:
-
POST /api/v1/mcp/servers?async=true -
PATCH /api/v1/mcp/servers/{serverId}?async=true -
POST /api/v1/mcp/servers/{serverId}/start?async=true -
POST /api/v1/mcp/servers/{serverId}/stop?async=true -
POST /api/v1/mcp/servers/{serverId}/health-check?async=true -
Poll with
GET /api/v1/jobs/{jobId}
MCP servers are also user-scoped shared resources. All instances of the same user share them.
Two kinds exist:
-
remote -
local
GET /api/v1/mcp/servers
POST /api/v1/mcp/servers
Remote example:
{
"kind": "remote",
"name": "Docs MCP",
"endpoint_url": "https://mcp.example.com",
"auth_type": "bearer",
"auth_secret": "token_xxx",
"headers": {
"X-Client": "maclawsrv"
}
}
Local example:
{
"kind": "local",
"name": "Filesystem MCP",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\workprj\\aicoder"
],
"env": {
"NODE_ENV": "production"
},
"auto_start": true,
"disabled": false
}
GET /api/v1/mcp/servers/{serverId}
PATCH /api/v1/mcp/servers/{serverId}
DELETE /api/v1/mcp/servers/{serverId}
POST /api/v1/mcp/servers/{serverId}/start
POST /api/v1/mcp/servers/{serverId}/stop
POST /api/v1/mcp/servers/{serverId}/health-check
GET /api/v1/mcp/servers/{serverId}/tools
curl -s http://127.0.0.1:18080/api/v1/admin/tenants \
-H 'X-MaClaw-Admin-Secret: your-admin-secret' \
-H 'Content-Type: application/json' \
-d '{"name":"Demo Tenant"}'
curl -s http://127.0.0.1:18080/api/v1/admin/tenants/tenant_xxx/users \
-H 'X-MaClaw-Admin-Secret: your-admin-secret' \
-H 'Content-Type: application/json' \
-d '{"name":"Demo User","email":"demo@example.com"}'
curl -s http://127.0.0.1:18080/api/v1/admin/tenants/tenant_xxx/users/user_xxx/credentials \
-H 'X-MaClaw-Admin-Secret: your-admin-secret' \
-H 'Content-Type: application/json' \
-d '{"name":"demo-client","api_key":"ak_demo","api_secret":"demo-secret"}'
curl -s http://127.0.0.1:18080/api/v1/auth/token \
-H 'Content-Type: application/json' \
-d '{"api_key":"ak_demo","api_secret":"demo-secret"}'
curl -s http://127.0.0.1:18080/api/v1/config \
-X PUT \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"maclaw_llm_url":"https://api.openai.com/v1","maclaw_llm_key":"sk-xxx","maclaw_llm_model":"gpt-5.4"}'
curl -s http://127.0.0.1:18080/api/v1/instances \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"name":"primary-agent","description":"demo"}'
curl -s http://127.0.0.1:18080/api/v1/instances/inst_xxx/messages \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"title":"Demo","content":"Please introduce your capabilities."}'
Applies to desktop AI assistants, chat frontends, IDE plugins, and agent shells.
Recommended call chain:
-
POST /api/v1/auth/token -
GET /api/v1/me -
GET /api/v1/config -
POST /api/v1/config/validate -
POST /api/v1/instances -
GET /api/v1/instances/{instanceId}/capabilities -
POST /api/v1/instances/{instanceId}/messages -
GET /api/v1/instances/{instanceId}/runs/{runId}orGET /api/v1/instances/{instanceId}/runs/{runId}/events -
GET /api/v1/instances/{instanceId}/sessions/{sessionId}/messages
Implementation focus:
-
Bind local state to
tenant_id + user_idimmediately after login. -
Treat the
messageslist as the source of truth for transcript rendering, not only therunresponse. -
If
waiting_for_user=true, readsession.pending_askbefore deciding the next UI step. -
Decide whether to show tools, ask-user, bash, or SSH features based on
capabilities.
Applies to SaaS consoles, provisioning backends, tenant admin pages, and ops panels.
Recommended call chain:
-
POST /api/v1/admin/tenants -
POST /api/v1/admin/tenants/{tenantId}/users -
POST /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials -
GET /api/v1/admin/tenants -
GET /api/v1/admin/tenants/{tenantId}/users -
GET /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials -
GET /api/v1/admin/overview -
GET /api/v1/admin/dashboard -
GET /api/v1/admin/alerts -
GET /api/v1/admin/audit-events
Implementation focus:
-
Admin consoles should store tenant/user/credential metadata, not end-user bearer tokens.
-
audit-eventsfits audit views, event timelines, and issue tracing screens. -
Tenant disable, user disable, and credential revoke should usually be presented as distinct actions in UI.
Applies to first-run setup, settings pages, and config diagnostics.
Recommended call chain:
-
GET /api/v1/config/schema -
GET /api/v1/config -
POST /api/v1/config/validate -
POST /api/v1/config/test -
PUT /api/v1/config -
POST /api/v1/instances/{instanceId}/refresh-readiness
Implementation focus:
-
Build fields from
config/schemarather than hardcoding them in frontend. -
Use
validatefor inline form validation andtestfor a dedicated connectivity-check button. -
After saving config, refresh instance readiness instead of forcing users to recreate the instance.
Applies to skill marketplaces, extension pages, and MCP management UI.
Recommended call chain:
-
GET /api/v1/skills -
POST /api/v1/skills/search -
POST /api/v1/skills/installorPOST /api/v1/skills/import -
POST /api/v1/skills/{skillName}/validate -
POST /api/v1/skills/{skillName}/upload -
GET /api/v1/mcp/servers -
POST /api/v1/mcp/servers -
POST /api/v1/mcp/servers/{serverId}/health-check -
GET /api/v1/mcp/servers/{serverId}/tools
Implementation focus:
-
Skills and MCP servers are user-scoped shared resources, not instance-private resources.
-
For MCP, run
health-checkbefore surfacingtoolsto users. -
Skill import, export, and upload may involve large payloads, so UI should handle progress and failure states.
Shortest useful path:
-
Admin creates tenant, user, and credential.
-
Client exchanges token.
-
Client fills config.
-
Client creates an instance.
-
Client reads capabilities.
-
Client sends the first message.
When create instance or send message returns a config error, the recommended recovery flow is:
-
Read
config_validation.issuesfrom the response. -
Open the settings page or config panel.
-
Call
GET /api/v1/config/schemato re-render field guidance. -
Let the user edit, then call
POST /api/v1/config/validate. -
If needed, call
POST /api/v1/config/test. -
Save and refresh instance readiness.
For a more real-time chat experience:
-
POST /api/v1/instances/{instanceId}/messages -
Read
run.id -
Open
GET /api/v1/instances/{instanceId}/runs/{runId}/eventsas SSE -
After
done, fetch message history once for final consistency
For products with a conversation history page:
-
GET /api/v1/instances/{instanceId}/sessions -
When a user opens a session, call
GET /messages -
When a user archives a session, call
POST /archive -
Use
include_archived=truein the archive/history view -
Call
POST /restorewhen restoring a session
-
Always call
GET /api/v1/meafter login and bind local client state totenant_id + user_id. -
Treat config as user-shared state, not instance-local state.
-
Treat skills as user-shared state.
-
Treat MCP servers as user-shared state.
-
Prefer
POST /instances/{id}/messagesunless explicit session lifecycle control is required. -
Use
GET /instances/{id}/capabilitiesto adapt UI based on tool exposure and policy. -
Use
waiting_for_userandpending_askto support structured follow-up interactions. -
Surface
config_validation.issuesdirectly to the user. -
Prefer run SSE when near-real-time status is needed; otherwise poll run status.
-
Do not assume coding-session orchestration APIs exist in
MaClawSrv.
A production-grade caller should support at least:
-
admin bootstrap flow or external provisioning flow
-
login and token refresh/re-login
-
config load, validate, update, and test
-
instance create, list, get, and state refresh
-
message send
-
session and run polling or SSE subscription
-
capability inspection
-
skill list/search/install/import/export/upload when relevant
-
MCP create/update/start/check/tools when relevant
-
structured error rendering
Admin:
-
GET /health -
GET /api/v1/admin/system/readiness -
GET /api/v1/admin/overview -
GET /api/v1/admin/dashboard -
GET /api/v1/admin/alerts -
GET /api/v1/admin/audit-events -
POST /api/v1/admin/tenants -
GET /api/v1/admin/tenants -
GET /api/v1/admin/tenants/{tenantId} -
PATCH /api/v1/admin/tenants/{tenantId} -
POST /api/v1/admin/tenants/{tenantId}/users -
GET /api/v1/admin/users -
GET /api/v1/admin/tenants/{tenantId}/users -
GET /api/v1/admin/tenants/{tenantId}/users/{userId} -
PATCH /api/v1/admin/tenants/{tenantId}/users/{userId} -
GET /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials -
POST /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials -
DELETE /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials/{credentialId}
Auth and user runtime:
-
POST /api/v1/auth/token -
GET /api/v1/me -
GET /api/v1/config/schema -
GET /api/v1/config -
PUT /api/v1/config -
POST /api/v1/config/validate -
POST /api/v1/config/test -
GET /api/v1/usage/summary -
GET /api/v1/instances -
POST /api/v1/instances -
GET /api/v1/instances/{instanceId} -
PATCH /api/v1/instances/{instanceId} -
DELETE /api/v1/instances/{instanceId} -
GET /api/v1/instances/{instanceId}/capabilities -
POST /api/v1/instances/{instanceId}/stop -
POST /api/v1/instances/{instanceId}/resume -
POST /api/v1/instances/{instanceId}/refresh-readiness -
GET /api/v1/instances/{instanceId}/bootstrap -
POST /api/v1/instances/{instanceId}/messages -
GET /api/v1/instances/{instanceId}/sessions -
POST /api/v1/instances/{instanceId}/sessions -
GET /api/v1/instances/{instanceId}/sessions/{sessionId} -
PATCH /api/v1/instances/{instanceId}/sessions/{sessionId} -
DELETE /api/v1/instances/{instanceId}/sessions/{sessionId} -
POST /api/v1/instances/{instanceId}/sessions/{sessionId}/archive -
POST /api/v1/instances/{instanceId}/sessions/{sessionId}/restore -
GET /api/v1/instances/{instanceId}/sessions/{sessionId}/messages -
POST /api/v1/instances/{instanceId}/sessions/{sessionId}/messages -
GET /api/v1/instances/{instanceId}/runs -
GET /api/v1/instances/{instanceId}/runs/{runId} -
GET /api/v1/instances/{instanceId}/runs/{runId}/events -
POST /api/v1/instances/{instanceId}/runs/{runId}/cancel
Skills:
-
GET /api/v1/skills -
POST /api/v1/skills/search -
POST /api/v1/skills/install -
POST /api/v1/skills/import -
GET /api/v1/skill-uploads/{submissionId} -
GET /api/v1/skill-market/account -
GET /api/v1/skills/{skillName} -
DELETE /api/v1/skills/{skillName} -
GET /api/v1/skills/{skillName}/export -
POST /api/v1/skills/{skillName}/validate -
POST /api/v1/skills/{skillName}/improve -
POST /api/v1/skills/{skillName}/upload
MCP:
-
GET /api/v1/mcp/servers -
POST /api/v1/mcp/servers -
GET /api/v1/mcp/servers/{serverId} -
PATCH /api/v1/mcp/servers/{serverId} -
DELETE /api/v1/mcp/servers/{serverId} -
POST /api/v1/mcp/servers/{serverId}/start -
POST /api/v1/mcp/servers/{serverId}/stop -
POST /api/v1/mcp/servers/{serverId}/health-check -
GET /api/v1/mcp/servers/{serverId}/tools
Delete safety: DELETE /api/v1/admin/tenants/{tenantId} and DELETE /api/v1/admin/tenants/{tenantId}/users/{userId} require ?confirm=true.
Recommended flow:
- Call the matching
delete-checkorretire-planendpoint. - Review counts, blockers, and export payload.
- Submit the final delete request with
?confirm=true.
Requests without confirm=true return 400 and do not delete data.
Delete protection is available on both tenant and user resources via delete_protected and delete_protection_reason.
Behavior:
- Set the flag when creating or updating a tenant or user.
delete-checkreportsdelete_protected=trueand returns adelete_protectedblocker.- Final delete requests still return
409until the protection flag is removed.
Server-generated credentials: omit api_key and/or api_secret when creating a credential.
POST /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials returns generated values only once in the create response. Store the returned api_secret on the client side if the caller needs to use that credential later; subsequent credential reads return only masked key information.
Credential expiry alerts: GET /api/v1/admin/alerts includes credentials that are expired or expiring soon.
Useful filters:
kind=credential_expiringreturns credentials whoseexpires_atis within the configured lookahead window.kind=credential_expiredreturns credentials whoseexpires_athas already passed.credential_expiry_window_dayschanges the lookahead window for expiring credentials. Default:7. Maximum:365.
Credential alert entries are sanitized; they do not return api_secret, secret_digest, or api_key_hash.
Server-generated credential rotation: omit api_secret on rotate-secret or api_key on rotate-key.
The generated plaintext value is returned only once in the rotation response. Later credential reads return masked key information and never return the plaintext secret.
Admin overview credential counters: overview responses include credential status and expiry-risk totals.
Fields include credentials, active_credentials, suspended_credentials, revoked_credentials, expired_credentials, and expiring_credentials. The expiring_credentials count uses the default 7-day lookahead window. Snapshot observability fields are also included: snapshots and snapshot_bytes.
GET /api/v1/admin/tenants/{tenantId}/summary returns credential lifecycle counters on the top-level tenant summary and on each user_summaries[] entry.
Fields include credentials, active_credentials, suspended_credentials, revoked_credentials, expired_credentials, and expiring_credentials. The expiring_credentials count uses the default 7-day credential expiry lookahead.
GET /metrics includes Prometheus credential lifecycle gauges:
maclaw_credentials_totalmaclaw_credentials_by_status{status="active"}maclaw_credentials_by_status{status="suspended"}maclaw_credentials_by_status{status="revoked"}maclaw_credentials_expired_totalmaclaw_credentials_expiring_total
GET /api/v1/usage/summary includes credential lifecycle counters for the authenticated tenant/user. The response includes credentials, active_credentials, suspended_credentials, revoked_credentials, expired_credentials, and expiring_credentials.
These are aggregate counters only; the endpoint does not reveal credential secrets, key hashes, or plaintext API keys.
POST /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials accepts optional expires_at in RFC3339/RFC3339Nano format.
Example:
{
"name": "CI runner",
"expires_at": "2026-05-01T00:00:00Z"
}When api_key or api_secret is omitted, the service still generates and returns the plaintext values only once in the create response.
GET /api/v1/admin/tenants/{tenantId}/users/{userId}/credentials supports filtering before pagination.
Supported query parameters:
status=active|suspended|revokedexpired=true|falseexpiring=true|falselimitandbeforefor pagination
expiring=true uses the default 7-day lookahead window. Invalid status, expired, or expiring values return 400.
GET /api/v1/admin/audit-events supports these additional filters:
resource_id: exact resource id, such as a credential id, run id, user id, or tenant id.actor_type: exact actor type, such asadmin,user,credential,system, oranonymous.
These filters can be combined with tenant_id, user_id, action, resource_type, limit, and before.
GET /api/v1/admin/audit-events supports since and until in RFC3339/RFC3339Nano format. They filter audit events by created_at before pagination is applied.
Use since/until for logical time windows. Use before as the pagination cursor returned by next_before.
Persisted snapshots turn the export pipeline into a manageable backup resource under MACLAW_DATA_ROOT/snapshots.
Create a snapshot:
POST /api/v1/admin/snapshots
X-MaClaw-Admin-Secret: <admin-secret>
Content-Type: application/json
{
"name": "tenant-a nightly backup",
"tenant_id": "tenant_xxx",
"user_id": "user_xxx",
"include_messages": true,
"include_runs": true,
"include_audit": true,
"include_secrets": false
}Notes:
tenant_idis optional. Omit it for a full service snapshot.user_idis optional, but requirestenant_id.include_messages,include_runs, andinclude_auditdefault totrue;include_secretsdefaults tofalse.- Snapshot files are private JSON files written below the service data root.
- The response is
{ "snapshot": <metadata>, "data": <ExportServiceStateOutput> }. - Snapshot counts and total snapshot bytes are visible in
GET /api/v1/admin/overviewandGET /metrics.
List snapshots:
GET /api/v1/admin/snapshots?tenant_id=tenant_xxx&user_id=user_xxx&scope=user&name=nightly&since=2026-04-01T00:00:00Z&until=2026-04-28T00:00:00Z&limit=100&before=2026-04-28T00:00:00Z
X-MaClaw-Admin-Secret: <admin-secret>List filters include tenant_id, user_id, scope=service|tenant|user, case-insensitive name, and since/until RFC3339 time windows.
Get one snapshot:
GET /api/v1/admin/snapshots/{snapshot_id}
X-MaClaw-Admin-Secret: <admin-secret>Restore a snapshot:
POST /api/v1/admin/snapshots/{snapshot_id}/restore?dry_run=true
X-MaClaw-Admin-Secret: <admin-secret>POST /api/v1/admin/snapshots/{snapshot_id}/restore?overwrite=true
X-MaClaw-Admin-Secret: <admin-secret>Restore notes:
- Restore reuses the same import pipeline as
POST /api/v1/admin/import. dry_run=truereturns conflicts, warnings, and plan items without mutating state.overwrite=trueis required when restored tenant/user IDs already exist.overwriteanddry_runcan be passed either as query parameters or JSON body fields.- Full credential restore requires snapshots created with
include_secrets=true.
Prune snapshots:
POST /api/v1/admin/snapshots/prune?tenant_id=tenant_xxx&user_id=user_xxx&older_than=2026-04-28T00:00:00Z&keep_latest=3&dry_run=true
X-MaClaw-Admin-Secret: <admin-secret>Prune notes:
older_thanis an RFC3339 timestamp cutoff.keep_latest=Nprotects the latest N snapshots in the filtered tenant/user scope.- At least one of
older_thanorkeep_latestis required. - Use
dry_run=truefirst to previewsnapshots,kept_snapshots, andfreed_bytes.
Delete a snapshot:
DELETE /api/v1/admin/snapshots/{snapshot_id}?confirm=true
X-MaClaw-Admin-Secret: <admin-secret>