REST API reference
ERPBridge Server exposes direct HTTP endpoints. These endpoints do not require an MCP handshake. They serve the bridgectl CLI, scripts, and monitoring tools.
Base URLโ
http://localhost:8080 (or the value of MCP_PORT)
Authenticationโ
HTTP authentication is enabled when API_AUTH_TOKEN is non-empty. Send the
configured credential as Authorization: Bearer <token>.
The admin credential is required for the registry, direct invoke, cache, and
token lifecycle endpoints. API tokens use scopes: mcp for /mcp/,
metrics for /metrics, and logs for the log endpoints. /mcp/health is
always open. Missing or invalid credentials return 401; insufficient scope
or a non-admin token on an admin route returns 403.
MockERP upstream contractโ
The local Compose stack uses the pinned ghcr.io/nmdra/mockerp:0.2.1 image at
http://mock-erp:8081. The matching OpenAPI specification is published at
https://raw.githubusercontent.com/nmdra/mockerp/v0.2.1/openapi.yaml.
MockERP is a downstream ERP fixture, not an ERPBridge control-plane endpoint. See the MockERP Integration Contract for credentials, SQLite reset, supported DocTypes, and integration fixtures.
Tool registry (control plane)โ
The registry API is Kubernetes-style. It stores tool definitions in SQLite.
List toolsโ
GET /apis/erpbridge.io/v1/tools
Returns a JSON array of tool definitions.
Use the optional exact-match query parameters name and version to request a specific tool version:
GET /apis/erpbridge.io/v1/tools?name=list_employees&version=1.0.0
Apply a toolโ
POST /apis/erpbridge.io/v1/tools
Content-Type: application/json
Body: one JSON tool definition (kind MCPTool). Tool names cannot contain the reserved .rev_ exact-revision marker. The authenticated apply creates an admission record and returns 201 Created with the canonical executable-content digest. Reapplying identical content is idempotent. ERPBridge does not replace an admitted version with changed content; it returns 409 REGISTRY_CONFLICT, and the operator must create and review a new version. The bridgectl tool apply command also accepts a YAML sequence or
multi-document YAML emitted by bridgectl tool generate and sends each tool
definition separately. Keep the reviewed manifest under
manifests/<module>/; generated output is a temporary draft, not a second
source of truth:
{
"status": "applied",
"name": "list_employees",
"version": "1.0.0",
"resourceDigest": "<sha256>"
}
When validation rejects the definition, the server returns 422 Unprocessable Entity. An immutable-revision conflict returns 409 Conflict.
Delete a toolโ
DELETE /apis/erpbridge.io/v1/tools?name=<name>&version=<version>&hard=true
| Query param | Description |
|---|---|
name | Tool name. Required. |
version | Tool version. Required. |
hard | true removes the row from SQLite. Omitted or false soft-deletes the tool. |
Returns 204 No Content on success.
External plugin registryโ
Plugin and binding resources use the admin-only Kubernetes-style control-plane routes:
| Resource | Apply/List | Delete |
|---|---|---|
| Plugin | POST/GET /apis/erpbridge.io/v1/plugins | DELETE .../plugins?name=<name>&version=<version> |
| PluginBinding | POST/GET /apis/erpbridge.io/v1/pluginbindings | DELETE .../pluginbindings?name=<name> |
Plugin deletion is soft by default. Use hard=true for permanent deletion. A
plugin with an active binding returns 409 Conflict. Binding admission requires
active exact plugin and tool versions. A raw_response binding additionally
requires configured API_AUTH_TOKEN, an authenticated admin request, an
allowlisted plugin endpoint, an active HTTP-backed tool, and an explicit
object-shaped output schema. Missing raw prerequisites prevent activation during
reconciliation. Use resource-specific name and version filters to list exact
records.
Plugin authentication and transportโ
A plugin can declare only bearer or API-key authentication. Its credentialRef
must name a logical PLUGIN_ reference. The resource stores that name, never
the resolved credential. credentialSource is optional and defaults to env.
Set it to file to read <ERPBRIDGE_CREDENTIALS_DIR>/<credentialRef> for each
request; file failures fail closed and never fall back to the environment.
API-key authentication may use a validated custom header, such as X-API-Key;
raw tokens, keys, authorization headers, and unknown authentication fields are
rejected at admission.
Credentialed plugin calls require HTTPS. A development-only HTTP exception
requires the exact normalized host:port in INSECURE_AUTH_ALLOWED_HOSTS, and
credentialed plugin admission also requires exact membership in
PLUGIN_ENDPOINT_ALLOWLIST. URL userinfo, query parameters, fragments,
broad wildcards, and redirect-based transport are rejected. Unauthenticated
plugins remain compatible with HTTP where the endpoint policy permits it.
Plugin HTTP protocolโ
For an active binding, ERPBridge sends a synchronous JSON request to
<spec.endpoint>/v1/process. An after_response binding receives the
normalized result:
{
"protocolVersion": "v1",
"invocationId": "generated-id",
"tool": {"name": "list_employees", "version": "1.0.0"},
"result": {"employees": []},
"config": {"mode": "safe"}
}
A raw_response binding instead receives the bounded ERP response before
normalization:
{
"protocolVersion": "v1",
"invocationId": "generated-id",
"tool": {"name": "read-invoice-text", "version": "1.0.0"},
"rawResponse": {
"status": 200,
"contentType": "image/png",
"body": {"encoding": "base64", "value": "..."}
}
}
encoding is json for one complete decoded JSON document. Empty, malformed,
and non-JSON bodies use base64. Raw invocations omit result; normalized
legacy invocations retain result: null when the result is nil. The plugin
returns {"result": <JSON value>} and can replace only the body; status remains
immutable. Request and response JSON are limited to 1 MiB; redirects and
retries are disabled.
For successful 2xx responses ERPBridge applies responsePath, validates the
declared output schema, runs after-response bindings, and validates the final
result. Terminal non-2xx responses, including 3xx, remain errors and skip
success-only processing. continue uses the original captured response only
when it satisfies the final schema; otherwise it returns a safe error. Use
fail for image conversion unless a compatible fallback is known. Plugin URLs,
payloads, credentials, and plugin error bodies are not returned to callers.
The protocol excludes original arguments, inbound headers, caller identity, caller tokens, and ERP credentials. Bound response processing runs only on cache misses; the cache stores the final transformed MCP result and never stores error results. Plugin and binding lifecycle changes flush affected target-tool cache entries.
Environment-backed references remain the default. Change the referenced
PLUGIN_* environment value and recreate or roll the deployment to rotate one.
For file-backed credentials, ERPBridge reads the mounted file immediately before
each request and bypasses affected tool cache reads and writes. Local writable
mounts should use a complete same-directory temporary file and atomic rename;
CSI/projected mounts update eventually and must use a directory, not Kubernetes
subPath. AWS EKS ASCP, AKS Key Vault CSI, and GKE Secret Manager CSI can
provide the mount. Use workload identity and least privilege. Adding a new
remote secret may require changing the provider mapping and rolling the
workload before its filename is mounted. bridgectl api test --local resolves
in the CLI process and needs the same mount; the normal probe resolves in
ERPBridge. Dynamic secret-provider SDKs, OAuth, mTLS, and application-level
encryption are not part of this contract.
Admission rulesโ
The server rejects plugin definitions with missing exact references, unsupported or malformed authentication, raw-secret-looking fields, reserved or hop-by-hop headers, disallowed endpoints, or credentialed HTTP without both allowlist checks. Plugin and binding routes require the admin credential.
The server rejects tool definitions when:
- The tool name starts with
get-orpost-. - The endpoint path contains embedded secrets (for example
tokenorkey=). spec.security.dataClassis notpublic,internal,pii, orrestricted.- A
piiorrestrictedtool has noallowedRoles. spec.security.allowedRolescontains invalid, duplicate, empty, or more than 32 roles.- A guarded tool defines or requires its own
roleinput.
Tool invocationโ
Invoke a toolโ
POST /api/tools/invoke
Content-Type: application/json
Body:
{
"name": "list_employees",
"arguments": {}
}
The call goes through the middleware chain: rate limiting, caching, and resilience.
Authenticated direct calls share a limiter bucket by token principal. Tool
execution resolves spec.security.credentialRef on the server and sends it in
spec.security.authHeader when configured; an omitted header preserves the
Authorization default. authHeader contains only a header name, never a
credential value.
Excess direct calls return HTTP 429 with the stable RATE_LIMITED error and a
whole-second Retry-After header. Streamable HTTP MCP tools/call requests
remain HTTP 200 responses with result.isError=true; tools/list is not
throttled.
For guarded tools, direct callers pass the verified selector in
X-ERPBridge-Role. A role field in the JSON body is rejected. The selector
must be present in both the caller identity and the tool allowedRoles list.
This endpoint resolves registered tools only. MCP built-ins such as system.progress_test are available through MCP tools/call, but not through this REST endpoint.
The REST endpoint returns the legacy ToolResult compatibility shape. MCP
clients receive the MCP result envelope, including content,
structuredContent for declared object-shaped output schemas, and
isError: true for tool execution errors. An image-to-text tool can expose
{text: string} as structured {text: ...} plus equivalent text containing
only the extracted text. SDK clients must preserve that envelope and must not
flatten structured content.
{
"result": {
"employees": [...]
}
}
On failure the server returns 500 Internal Server Error with a JSON error envelope:
{
"error": "tool execution failed: ..."
}
Cacheโ
| Endpoint | Method | Description |
|---|---|---|
/api/cache/stats | GET | Cache key counts and memory usage. Works with Redis and the bounded in-memory backend; returns 503 HEALTH_CHECK_FAILED when configured Redis is unavailable. |
/api/cache/flush | GET | Flush cache entries. Query params: tool, module, all=true. A module flush covers all stored versions, including inactive versions. |
Cache stats responseโ
{
"apiVersion": "v1",
"kind": "CacheStats",
"status": "active",
"stats": {
"exactKeys": 42,
"redisMemory": "1.2 MiB"
}
}
Flush responseโ
{
"deleted": 3,
"status": "ok"
}
400 Bad Request is returned when tool, module, and all are all absent.
Logsโ
| Endpoint | Method | Description |
|---|---|---|
/api/logs/recent | GET | JSON array of the last 1000 log entries. |
/api/logs/stream | GET | Server-sent events stream of log entries. |
OpenAPI-generated tools preserve path, query, header, and JSON body
parameters through execution.parameterLocations; see the tool schema
reference for the legacy fallback and protected-header
rules. Generated response unwrapping is used only when the resolved top-level
response schema proves a data property.
/api/logs/stream flushes its 200 response headers before the first event.
Each event uses data: <JSON>\n\n framing. Closing the client request stops
subscription. Subscribers use a bounded 100-event channel; new events are
dropped for a slow subscriber when that channel is full. The raw stream is
separate from the Console BFF, which applies its own projection and redaction.
Token lifecycleโ
| Endpoint | Method | Description |
|---|---|---|
/api/auth/tokens | POST | Create a scoped API token. Admin only. |
/api/auth/tokens | GET | List token metadata without raw values or hashes. Admin only. |
/api/auth/tokens/{id} | DELETE | Revoke a token. Admin only. |
MCP endpointsโ
| Endpoint | Method | Description |
|---|---|---|
/mcp/ | POST | MCP JSON-RPC requests (Streamable HTTP). |
/mcp/ | GET | SSE notification stream for the session. |
/mcp/health | GET | Returns {"status": "ok"}. |
Guarded MCP tools advertise an optional arguments.role selector. The server
validates it against the authenticated identity and tool allow-list, then
removes it before the ERP call. Open tools keep role as a normal business
argument. During tools/list, guarded tools also publish their non-empty
allow-list as io.erpbridge/allowedRoles in namespaced _meta for host/model
reference. This metadata does not grant access and may not reach the model
through every MCP client. Guarded read-only cache entries are shared; other
guarded entries are scoped by verified role.
Observabilityโ
| Endpoint | Method | Description |
|---|---|---|
/metrics | GET | Prometheus-formatted metrics. |
/api/info | GET | Authenticated safe build and runtime metadata. |
/api/info returns the ERPBridge version, optional commit and build date, cache backend label, active tool count, observation timestamp, and safe process-local reconciliation status. Reconciliation fields include attempt count, last attempt and success, safe error state, desired-state hash, observed generation, and convergence. Cycle measurements reset on restart; desired resources and withdrawal tombstones remain in SQLite. It does not return credentials or ERP configuration. Older deployments may not implement
this optional endpoint; clients should treat a 404 as unavailable.
Prometheus Metricsโ
The server exposes the following metrics in Prometheus text format. All counters are cumulative.
| Metric | Type | Labels | Description |
|---|---|---|---|
erp_requests_total | Counter | method, path, status | Outbound requests from the connector to the upstream ERP. |
erp_request_duration_seconds | Histogram | method, path | Latency distribution of outbound ERP requests. |
mcp_tool_invocations_total | Counter | tool, cache_status (SUCCESS/ERROR) | MCP tool invocations. |
mcp_tool_duration_seconds | Histogram | tool | End-to-end duration of MCP tool calls. |
cache_hits_total | Counter | type (exact) | Cache hits. |
cache_misses_total | Counter | โ | Cache misses. |
credential_resolutions_total | Counter | source, outcome | Credential lookup outcomes. Labels never contain credential references, values, paths, or hashes. |
mcp_server_starts_total | Counter | โ | MCP server startup events. |
mcp_server_stops_total | Counter | โ | MCP server shutdown events. |
mcp_sessions_started_total | Counter | โ | MCP client sessions established. |
mcp_sessions_ended_total | Counter | โ | MCP client sessions terminated. |
mcp_sessions_active | Gauge | โ | Currently active MCP client sessions. |
curl http://localhost:8080/metrics | grep -E "erp_requests_total|mcp_tool_invocations_total"
Error responsesโ
The server uses standard HTTP status codes. Control-plane failures return a
bounded JSON object with error, message, suggestion, and numeric code.
The stable error value is safe for automation. Common values include
CONTEXT_NOT_FOUND, LEGACY_REGISTRY, REGISTRY_CONFLICT,
CONTROL_PLANE_URL_INVALID, VALIDATION_FAILED, AUTHENTICATION_FAILED,
AUTHORIZATION_DENIED, UPSTREAM_UNREACHABLE, INSECURE_TRANSPORT,
HEALTH_CHECK_FAILED, RECONCILIATION_FAILED, RESOURCE_NOT_FOUND, and
API_PROBE_FAILED. Messages and suggestions do not contain upstream bodies,
credentials, auth headers, URLs with secrets, or stack details. MCP execution
failures remain successful protocol responses with isError: true.
API probeโ
POST /api/apis/test is an authenticated administrator endpoint. It accepts a
non-secret API definition, credentialRef, optional credentialSource
(env by default or file), and optional auth-header name. File mode requires
ERPBRIDGE_CREDENTIALS_DIR, reads the reference-named file for each probe, and
never falls back to the environment. The server resolves the credential and
applies its ERP transport policy. It returns only status, normalized content
type, latency, and success.
ERP response bodies and upstream headers never cross the probe boundary.
bridgectl api test uses this route by default; --local is an explicit
host-side diagnostic that resolves files in the CLI process.
For cache endpoints, 503 HEALTH_CHECK_FAILED means the cache is disabled or
the selected Redis backend is unavailable. The server does not silently replace
configured Redis with memory.
See the MCP client guide for the JSON-RPC request/response flow over the MCP endpoints.