Public API and MCP Server

Overview

ET Ducky has a read-only API for outside dashboards, scripts and AI assistants. It has two surfaces that use the same credential and return the same data:

  • REST endpoints under /api/v1 for agents, alerts and a fleet summary.
  • An MCP endpoint at /api/mcp that offers the same reads as tools for AI assistants that speak the Model Context Protocol.

Both are authenticated with an organization API token. A token reads one organization. Nothing on either surface changes data.

API tokens

Tokens are managed in Team → Workspace → API Tokens. Organization admins and owners can see the card's token list, create tokens and revoke them, and so can members holding the API editor privilege. It is granted like the other member privileges, for one organization or for the whole workspace. Other members see a message saying who can manage tokens. A token keeps working after the member who created it loses the privilege or leaves the organization; the card shows each token's creator so it can be revoked by hand. Tokens belong to the organization that is active in the dashboard.

Create a token

  1. Enter a name of 1 to 100 characters, for example the dashboard or script that will use it.
  2. Pick an expiry: 30 days, 90 days, 1 year (365 days), or never. The form starts at 90 days.
  3. Select Create token. The token is shown once, starting with etd_api_. Copy it before you select Done.

Only a hash of the token is stored, so it cannot be shown again. If a token is lost, revoke it and create another. An organization can hold at most 25 live tokens (tokens that are neither revoked nor expired).

Token list and revocation

The list shows each token's name, its last four characters, when it was created, when it was last used and its status. Last use is updated at most once every 5 minutes. Revoke stops the token on its next request; the row stays in the list as a record. Creating and revoking a token are recorded in the audit log. A revoked or expired token is answered with 401.

A token reads every agent's hostname, OS, last signed-in user, isolation state and alerts for its organization. Store it as you would a password, give each integration its own token, and set an expiry where the integration allows it.

Authentication and base URL

Send the token in the Authorization header on every request:

Authorization: Bearer etd_api_...

The base URL is your workspace address, https://<workspace>.etducky.com/api/v1, or https://etducky.com/api/v1. The token decides which organization is read, whichever host the request is sent to. On a self-hosted instance, use the instance's own address (see Self-hosted instances).

curl -H "Authorization: Bearer etd_api_..." \
  "https://<workspace>.etducky.com/api/v1/agents?os=windows&online=false"

Responses are JSON with camelCase field names. Timestamps are UTC in ISO 8601 form with a Z suffix. An agent is online when it has sent a heartbeat in the last 5 minutes. Deleted agents are never returned.

Endpoints

Method and pathReturns
GET /api/v1/agentsThe organization's agents, sorted by hostname.
GET /api/v1/agents/{agentId}One agent, its latest health sample and its number of active alerts.
GET /api/v1/alertsAlerts raised for the organization's agents, newest first.
GET /api/v1/summaryAgent counts by state and OS, and active alerts by severity.

GET /api/v1/agents

Query parameterMeaning
searchPart of the hostname, case-insensitive.
oswindows, linux or other. other covers agents with no recognised OS. Any other value is answered 400.
onlinetrue for agents with a heartbeat in the last 5 minutes, false for the rest.
tagOnly agents carrying this exact tag name.
limitMost agents to return, 1 to 500. Defaults to 100. A value outside that range is clamped to it.

The response is { "agents": [...], "total": n, "truncated": true|false }. total counts every agent that matches the filters, and truncated is true when limit cut the list short. Each agent has:

FieldMeaning
agentIdThe agent's id. Use it with /agents/{agentId} and the agentId alert filter.
hostnameThe device's hostname.
osVersionThe OS version text the agent reports.
osFamilywindows, linux or other.
agentVersion, agentTypeThe installed agent version and type.
isActiveWhether the agent is active in the organization.
online, lastHeartbeatOnline state and the time of the last heartbeat.
lastLogonUserThe last signed-in user the agent reported, or null.
isolationStateThe network isolation state, normal when the device is not isolated.
tagsThe agent's tag names.

GET /api/v1/agents/{agentId}

Returns { "agent": {...}, "latestHealth": {...}, "activeAlerts": n }. agent has the fields above. latestHealth is the newest health sample, or null when there is none, with timestamp, cpuUsagePercent, memoryUsagePercent, diskCFreePercent, uptimeSeconds, pendingReboot, systemManufacturer and systemModel. An id that is not an agent of the organization is answered 404 with {"error":"agent_not_found"}.

GET /api/v1/alerts

Query parameterMeaning
statusactive (the default), acknowledged, resolved, auto_resolved, suppressed, or any for no status filter. Any other value is answered 400.
severityOnly alerts of this severity, for example critical or warning.
agentIdOnly alerts for this agent.
sinceHoursHow far back to look, by the time the alert was raised: 1 to 720 hours. Defaults to 168 (7 days). A value outside that range is clamped to it.
limitMost alerts to return, 1 to 500. Defaults to 100.

The response is { "alerts": [...], "total": n, "truncated": true|false }. Each alert has alertId, ruleName, agentId, hostname, severity, status, triggeredAt, resolvedAt and acknowledgedAt.

GET /api/v1/summary

Returns agents, online, offline, windows, linux, other, isolated, activeAlerts, activeAlertsBySeverity (an object keyed by severity) and generatedAt.

Errors

StatusBodyWhen
400{"error":"invalid_filter","message":"..."}An os or status value outside its list. The message names the accepted values.
401{"error":"invalid_token"}The token is missing, malformed, unknown, revoked or expired, or its organization is no longer active. The response carries WWW-Authenticate: Bearer error="invalid_token".
404{"error":"agent_not_found"}The agent id in /agents/{agentId} is not an agent of the token's organization.
429JSON error bodyA rate limit was reached. Wait for the number of seconds in Retry-After.

Unexpected server errors are answered with an application/problem+json body that carries a correlationId and a traceId. Responses also carry the correlation id in the X-Correlation-ID header. Quote it when you contact support. See API Rate Limits for the error format.

Rate limits

Two limits apply to /api/v1 and /api/mcp together:

LimitValueX-RateLimit-Policy
Per client address600 requests a minute, counted for every request whether or not its token is validpublic-api-per-ip
Per token120 requests a minute, counted after the token is acceptedpublic-api-per-token

A request over either limit is answered 429 with Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining: 0 and X-RateLimit-Policy. Requests are not queued.

MCP endpoint

The MCP endpoint is https://<workspace>.etducky.com/api/mcp, or https://etducky.com/api/mcp. It authenticates with the same API token, sent as an Authorization: Bearer header.

  • It uses the Streamable HTTP transport without sessions. Each POST carries one JSON-RPC 2.0 message and is answered with one JSON response. GET is answered 405, because the server sends no stream. A JSON array (a batch) is answered 400.
  • It negotiates protocol version 2025-06-18 by default and also accepts 2025-11-25 and 2025-03-26. A request whose MCP-Protocol-Version header names another version is answered 400.
  • A request that carries an Origin header is refused with 403, so a web page in a browser cannot call the endpoint.
  • OAuth is not offered yet. The client must be able to send the token as a header.

Tools

All four tools are marked read-only. Each returns the same JSON as the matching REST endpoint, as text and as structured content.

ToolArgumentsSame data as
list_agentssearch, os, online, tag, limitGET /api/v1/agents
get_agentagent_id (required)GET /api/v1/agents/{agentId}
list_alertsstatus, severity, agent_id, since_hours, limitGET /api/v1/alerts
get_fleet_summarynoneGET /api/v1/summary

Arguments take the same values and defaults as the query parameters. An os or status value outside its list is answered with JSON-RPC error -32602. An unknown agent id in get_agent returns a tool result marked as an error with agent_not_found.

Connecting an AI assistant

Any MCP client that supports the Streamable HTTP transport and lets you set a request header can connect. Replace <workspace> with your workspace name and etd_api_... with your token.

Claude Code

claude mcp add --transport http etducky https://<workspace>.etducky.com/api/mcp \
  --header "Authorization: Bearer etd_api_..."

Add --scope user to make the server available in every project. To share the server through a project's .mcp.json without writing the token into the file, reference an environment variable:

{
  "mcpServers": {
    "etducky": {
      "type": "http",
      "url": "https://<workspace>.etducky.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${ETDUCKY_API_TOKEN}"
      }
    }
  }
}

Cursor

Add the server to ~/.cursor/mcp.json for every project, or to .cursor/mcp.json in one project:

{
  "mcpServers": {
    "etducky": {
      "url": "https://<workspace>.etducky.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${env:ETDUCKY_API_TOKEN}"
      }
    }
  }
}

Clients that need OAuth

Clients that connect to remote servers only through OAuth, or that cannot send a custom header, cannot connect yet. This includes custom connectors added in Claude Desktop and on claude.ai.

Self-hosted instances

A Local-First instance serves the same API and MCP endpoint at its own address: https://<your-instance>/api/v1 and https://<your-instance>/api/mcp. Tokens are created in the instance's own dashboard, in the same place, and read the instance's data.

When the license grace period has ended and the instance is collection-only, both surfaces still answer reads and a token can still be revoked. Creating a token is refused until the subscription is renewed. See Self-Hosted Deployment.