Agents
Connectors and approvals
LeanConnect connectors let agents act inside the services your team already uses, with credentials the workspace stores and the model never sees. One completed action is one call at the connector's rate; refused, failed and timed-out actions cost nothing.
The connector directory lists every connector by category with its actions; GET /api/v1/connectors answers the ones switched on for your workspace.
Endpoints#
{ name, credentialTypeKey, data } → 201 credential{ ok, id }inputSchema and outputHint, credential requirement{ credentialId | credential, input } → { output, usage }List and describe#
GET /api/v1/connectors answers [{ key, name, description, category, version, credentialTypes[], actions[{ key, name, description, isReadOnly, isDestructive }], pricing { perCall } }]. GET /api/v1/connectors/{key} adds each action's inputSchema (JSON Schema of the input object) and outputHint, plus credential { required, types[] }. Actions the workspace disabled in the console are not listed.
Credentials#
Credentials belong to the workspace. Link them in the console (Tools & Connectors → connector → Credentials) or through the API:
curl -s -X POST https://YOUR-HOST/api/v1/connectors/credentials \
-H "Authorization: Bearer lt-..." -H "Content-Type: application/json" \
-d '{ "name": "Twilio prod", "credentialTypeKey": "Twilio", "data": { "AccountSid": "AC...", "AuthToken": "..." } }'{ "id": "7c1f0a...", "name": "Twilio prod", "credentialTypeKey": "Twilio", "status": "Active", "lastError": null, "createdTime": "2026-09-27T09:00:00+00:00", "lastUsedTime": null }data holds the properties listed by GET /api/v1/connectors/credential-types (properties[{ name, type, required, isSecret, options }]); secrets are encrypted at rest and never returned. Another workspace's id is 404 credential_not_found. Types that need a browser consent flow (OAuth) are linked from the console. At most LeanTools.Connectors.MaxCredentialsPerAccount (50) per workspace. A credential the upstream rejects is marked Invalid and the console shows a "needs attention" badge.
Run an action#
curl -s -X POST https://YOUR-HOST/api/v1/connectors/Twilio/actions/sms.send \
-H "Authorization: Bearer lt-..." -H "Content-Type: application/json" \
-d '{ "credentialId": "7c1f0a...", "input": { "from": "+15550001111", "to": "+60123456789", "body": "Hello" } }'{ "connector": "Twilio", "action": "sms.send", "output": { "sid": "SM...", "status": "queued" },
"usage": { "credits": 1, "balance": 1868.15, "durationMs": 412, "upstreamProvider": "Twilio" } }Pass credentialId (a stored credential), credential: { credentialTypeKey, data } (used once, never persisted), or nothing to use the connector's default credential set in the console. input is validated against the action's inputSchema before anything runs (400 invalid_input / unknown_field, 413 payload_too_large over LeanTools.Connectors.MaxInputBytes). Over MCP the same action is connector.twilio.sms.send with arguments: { input, credentialId? }.
Guardrails#
Each connector has a configure drawer in the console with four guardrails, enforced on every call whether it arrives over REST or MCP:
| Guardrail | Effect | Refusal |
|---|---|---|
| Functions | Each action can be exposed or hidden from agents. Hidden actions vanish from the catalogue. | 403 action_disabled |
| Allowed keys | Restrict the connector to specific API keys (all keys by default). | 403 key_not_allowed |
| Rate limit and daily cap | Calls per minute and calls per UTC day across the workspace. | 429 rate_limited, 429 daily_cap_reached |
| Require approval for writes | Actions that are not read-only are parked until a member with the approve calls capability decides. | 202 approval_pending |
Approvals: human in the loop#
With require approval for writes on, a write action does not run. The call answers 202 with an approval id and expiry; the request appears in the console's Activity → Approvals queue with its arguments, where an Owner or Admin approves (the action then runs with the workspace credential, and the credits are charged) or denies it. Pending requests expire after LeanTools.Tenant.ApprovalExpiryMinutes (30 by default).
{ "isSuccess": false, "errorCode": "approval_pending",
"errorMessage": "The action 'update_deal' of connector 'hubspot' is held for human approval (request a1b2c3…). Poll GET /api/v1/connectors/approvals/a1b2c3… for the decision and the output; the request expires at 2026-09-28T10:30:00.0000000+00:00.",
"details": { "approvalId": "a1b2c3…", "expiresUtc": "2026-09-28T10:30:00+00:00", "pollUrl": "/api/v1/connectors/approvals/a1b2c3…" } }Poll the decision with the same key (a free management call):
{ "id": "a1b2c3…", "connector": "hubspot", "action": "update_deal", "status": "executed",
"createdUtc": "2026-09-28T10:00:00+00:00", "expiresUtc": "2026-09-28T10:30:00+00:00", "decidedUtc": "2026-09-28T10:04:12+00:00",
"output": { "id": "deal_123", "stage": "closedwon" }, "usage": { "credits": 1 } }status is pending, executing (claimed by an approver, the call is running), executed (approved and run; output and usage are filled), denied, expired or failed (approved but the call could not run: the upstream rejected it, or its key was revoked in the meantime, key_revoked; an approved call is bounded to 10 minutes, and a claim that never finished because the node died mid-call is failed with execution_interrupted after 15 minutes, which polling a due request settles; an executed request whose charge could not be recorded at once carries error { code: "billing_deferred" }, the call ran and the charge is recorded later); denied and failed requests carry an error { code, message }. Over MCP the held call is an isError: true result with the same fields, and the agent can call the connector again after the decision or wait for it, as the harness prefers. Approvals also carry the note the reviewer wrote, so the audit trail is complete.
Bring your own server#
The Generic MCP connector type registers any remote MCP server (Streamable HTTP) as a connector: provide its URL and, as the credential, the header it expects. Its tools become connector.<key>.<tool> actions, metered per completed call, listed with the annotations the server declares, and subject to the same guardrails and approvals as built-in connectors. Stdio transports are refused (403 transport_not_allowed).
Status codes#
| Status | Code | Meaning |
|---|---|---|
| 202 | approval_pending | Held for a human decision; poll the approval |
| 400 | invalid_request, invalid_input, unknown_field, credential_required, invalid_credential, oauth_not_supported, expressions_not_supported | Body shape, input validation, credential missing / wrong type / disabled |
| 401 | authentication_error, key_expired | Missing, unknown or expired bearer |
| 402 | insufficient_credits, key_cap_reached, tool_budget_reached | No credits, or a cap reached |
| 403 | service_not_allowed, tool_disabled, connector_disabled, connector_paused, action_disabled, key_not_allowed, transport_not_allowed | Key scope, a tool switched off, a connector switched off by the host (connector_disabled) or paused by the workspace (connector_paused), a hidden action, a key the connector does not allow |
| 404 | connector_not_found, action_not_found, credential_not_found, credential_type_not_found, approval_not_found | |
| 413 | payload_too_large | input over the host limit |
| 422 | connector_error | The upstream rejected the request (0 credits) |
| 429 | rate_limited, daily_cap_reached | Workspace guardrails |
| 502 / 504 | connector_upstream_error, connector_timeout | The upstream failed or did not answer within LeanTools.Connectors.CallTimeoutSeconds (0 credits) |