Skip to main content
Version: ERPBridge + bridgectl ยท v0.5.0-alpha.2

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โ€‹

List tool definitions
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โ€‹

Apply a tool definition
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:

Apply response
{
"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 a tool
DELETE /apis/erpbridge.io/v1/tools?name=<name>&version=<version>&hard=true
Query paramDescription
nameTool name. Required.
versionTool version. Required.
hardtrue 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:

ResourceApply/ListDelete
PluginPOST/GET /apis/erpbridge.io/v1/pluginsDELETE .../plugins?name=<name>&version=<version>
PluginBindingPOST/GET /apis/erpbridge.io/v1/pluginbindingsDELETE .../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- or post-.
  • The endpoint path contains embedded secrets (for example token or key=).
  • spec.security.dataClass is not public, internal, pii, or restricted.
  • A pii or restricted tool has no allowedRoles.
  • spec.security.allowedRoles contains invalid, duplicate, empty, or more than 32 roles.
  • A guarded tool defines or requires its own role input.

Tool invocationโ€‹

Invoke a toolโ€‹

Invoke a tool directly
POST /api/tools/invoke
Content-Type: application/json

Body:

Request 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.

Success response
{
"result": {
"employees": [...]
}
}

On failure the server returns 500 Internal Server Error with a JSON error envelope:

Error response
{
"error": "tool execution failed: ..."
}

Cacheโ€‹

EndpointMethodDescription
/api/cache/statsGETCache 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/flushGETFlush cache entries. Query params: tool, module, all=true. A module flush covers all stored versions, including inactive versions.

Cache stats responseโ€‹

GET /api/cache/stats
{
"apiVersion": "v1",
"kind": "CacheStats",
"status": "active",
"stats": {
"exactKeys": 42,
"redisMemory": "1.2 MiB"
}
}

Flush responseโ€‹

GET /api/cache/flush?tool=list_employees
{
"deleted": 3,
"status": "ok"
}

400 Bad Request is returned when tool, module, and all are all absent.

Logsโ€‹

EndpointMethodDescription
/api/logs/recentGETJSON array of the last 1000 log entries.
/api/logs/streamGETServer-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โ€‹

EndpointMethodDescription
/api/auth/tokensPOSTCreate a scoped API token. Admin only.
/api/auth/tokensGETList token metadata without raw values or hashes. Admin only.
/api/auth/tokens/{id}DELETERevoke a token. Admin only.

MCP endpointsโ€‹

EndpointMethodDescription
/mcp/POSTMCP JSON-RPC requests (Streamable HTTP).
/mcp/GETSSE notification stream for the session.
/mcp/healthGETReturns {"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โ€‹

EndpointMethodDescription
/metricsGETPrometheus-formatted metrics.
/api/infoGETAuthenticated 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.

MetricTypeLabelsDescription
erp_requests_totalCountermethod, path, statusOutbound requests from the connector to the upstream ERP.
erp_request_duration_secondsHistogrammethod, pathLatency distribution of outbound ERP requests.
mcp_tool_invocations_totalCountertool, cache_status (SUCCESS/ERROR)MCP tool invocations.
mcp_tool_duration_secondsHistogramtoolEnd-to-end duration of MCP tool calls.
cache_hits_totalCountertype (exact)Cache hits.
cache_misses_totalCounterโ€”Cache misses.
credential_resolutions_totalCountersource, outcomeCredential lookup outcomes. Labels never contain credential references, values, paths, or hashes.
mcp_server_starts_totalCounterโ€”MCP server startup events.
mcp_server_stops_totalCounterโ€”MCP server shutdown events.
mcp_sessions_started_totalCounterโ€”MCP client sessions established.
mcp_sessions_ended_totalCounterโ€”MCP client sessions terminated.
mcp_sessions_activeGaugeโ€”Currently active MCP client sessions.
Scrape example
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.

Need the full lifecycle?

See the MCP client guide for the JSON-RPC request/response flow over the MCP endpoints.