klanex API guide
Everything you need to run agent tool calls through klanex. Base URL: https://api.klanexai.com · auth header: X-API-Key · machine-readable spec: openapi.yaml
Sandbox: https://api.sandbox.klanexai.com runs the same code with Stripe in test mode — sign up there with a test card and integrate without touching production. Accounts, API keys, and data are separate per environment, and sandbox data carries no durability promise. Create a sandbox account →
Quickstart
- Sign up — verify your email, put a card on file, and save the
api_keyandwebhook_secretshown once. - Submit your first execution:
curl -s https://api.klanexai.com/v1/executions \
-H "X-API-Key: klx_…" -H "Content-Type: application/json" \
-d '{
"target": { "url": "https://httpbingo.org/post", "method": "POST" },
"payload": { "amount": 42 },
"payload_schema": { "type": "object",
"properties": { "amount": { "type": "number" } },
"required": ["amount"] },
"idempotency_key": "demo-1"
}'
# → 202 {"execution_id": "exe_…", "status": "QUEUED"}
- Poll
GET /v1/executions/{id}(or receive a signed webhook) and watch it in the dashboard.
Execution lifecycle
klanex answers in milliseconds and executes asynchronously. Statuses:
PENDING_APPROVAL → QUEUED → RUNNING → RETRYING (loops) → SUCCEEDED | FAILED
Retryable target failures (429, 5xx, timeouts) are absorbed with exponential backoff and per-host circuit breakers. Permanent rejections (4xx) fail fast with a correction hint for your agent.
Submit an execution
POST /v1/executions
| Field | Meaning |
|---|---|
target.url, target.method | The third-party call to make. Default method POST. |
target.headers | Credentials for the target. Encrypted at rest (Cloud KMS); never readable again, redacted in all reads. |
target.connection_id | Use a vault connection instead of raw headers. |
target.timeout_ms | Per-attempt timeout. |
payload | The JSON body your agent produced. Stored byte-exact for replay. |
payload_schema | JSON Schema gate. Invalid payloads bounce with 422 + llm_hint — feed it back to your model and resubmit. |
callback_url | Where to POST the signed result webhook. |
max_attempts | Retry budget. |
idempotency_key | Same key → same execution returned (200 + X-Klanex-Idempotent-Replay: true), never a duplicate run. |
requires_approval | Pause in PENDING_APPROVAL until a human decides — in the dashboard or right from Slack. |
Read & list
GET /v1/executions/{id} — full state; credentials always redacted.
GET /v1/executions?status=&since=&until=&limit=&cursor= — newest first, cursor-paginated.
Human-in-the-loop approvals
POST /v1/executions/{id}/approve · POST /v1/executions/{id}/reject (optional {"reason": "…"})
Executions submitted with requires_approval wait in PENDING_APPROVAL. Rejection is terminal (APPROVAL_REJECTED). Replays of gated executions pause again — approvals never carry over. With Slack connected, approvals are one button click.
Replay
POST /v1/executions/{id}/replay — re-run a terminal execution with its byte-exact original payload and sealed credentials; the clone records replay_of for audit.
POST /v1/replays {"since": "…", "until": "…"} — bulk-replay failures after an outage; permanently rejected payloads are skipped.
Webhooks
On terminal state klanex POSTs to your callback_url:
{
"event": "execution.completed", // or "execution.failed"
"execution_id": "exe_…",
"status": "SUCCEEDED",
"attempts": 2,
"result": { "status_code": 200, "body": "{…}" },
"error": null
}
| Header | Meaning |
|---|---|
X-Klanex-Signature | sha256= + hex HMAC-SHA256 of "<timestamp>.<body>" keyed with your webhook_secret |
X-Klanex-Timestamp | Unix seconds used in the signature — reject stale values to prevent replay |
X-Klanex-Event | Event name |
X-Klanex-Execution-Id | Execution ID |
Respond 2xx; 5xx responses are retried 3 times with backoff. Both SDKs ship a verifyWebhook / verify_webhook helper.
Error codes
Codes are part of the contract — feed llm_hint back to your agent so it can self-correct.
| Code | Terminal? | Meaning |
|---|---|---|
SCHEMA_INVALID | yes (422 at submit) | Payload failed your schema — fix and resubmit. |
TARGET_TIMEOUT | retried | Target didn't answer in time. |
TARGET_RATE_LIMITED | retried | Target returned 429, or another rejection whose response reports a rate limit. |
TARGET_UNAVAILABLE | retried | Target 5xx / connection failure, or a rejection whose response reports a temporary condition. |
TARGET_REJECTED | yes | Target 4xx, or a 2xx whose body reports a failure. Retrying won't help; correct the request as the llm_hint and diagnosis say. |
CIRCUIT_OPEN | retried | Host breaker open; klanex waits it out. |
ATTEMPTS_EXHAUSTED | yes | Retry budget spent. |
APPROVAL_REJECTED | yes | A human said no. |
CONNECTION_UNAUTHORIZED | yes | Vault connection can't mint a valid token, or the target rejected its credential. Reauthorize it. |
RATE_LIMITED | 429 at submit | Your tenant rate limit; honor Retry-After. |
Failures inside 2xx responses
Some APIs report errors with a success status: Slack returns {"ok": false}, GraphQL returns an errors array. klanex checks every 2xx body and fails the execution as TARGET_REJECTED when the body says the operation did not happen.
- Rules run on every response: a top-level
"ok": falseor"success": false, a"status"oferror,fail,failedorfailure, or a non-emptyerrorsarray with nodata. An explicit"ok": trueor"success": trueis accepted as is. - A model check covers the rest. Bodies that mention failure words (
error,invalid,denied, ...) get a yes/no judgment from TypeSafe's Jev model, and only a confident "this reports a failure" fails the execution. An unsure answer or an unavailable model keeps the success. See what the model sees.
The failed execution keeps result (status code and body) so you can see exactly what the target said, and error.message names the check that fired. Bodies that merely mention an error as data, such as a list of log entries, stay successful.
Diagnosing rejections
When a call fails (a 4xx, or a 2xx whose body reports failure), klanex reads the response to find the cause and, when the error points at one, the payload field. Both land in error.diagnosis, and the llm_hint is written for that cause:
"error": {
"code": "TARGET_REJECTED",
"llm_hint": "The target API rejected the request with status 400 because of the payload field `amount`. …",
"diagnosis": { "cause": "invalid_payload", "field": "amount" }
}
| Cause | Meaning | What klanex does |
|---|---|---|
invalid_payload | A field is missing, malformed, out of range, or stale (outdated version or etag). | TARGET_REJECTED; the hint names the field when known. |
auth | Credential missing, invalid, expired, or revoked. Every 401. | TARGET_REJECTED, or CONNECTION_UNAUTHORIZED for a vault connection. |
permission | Authenticated but not allowed: scope, role, plan, or quota. | TARGET_REJECTED; the hint says not to retry. |
not_found | The endpoint or a referenced ID or name does not exist. | TARGET_REJECTED; the hint says to look the value up, not guess. |
already_exists | A duplicate, or the action was already performed. | TARGET_REJECTED, or SUCCEEDED when klanex's own earlier attempt did it. |
rate_limited | The API asks to slow down, whatever the status code. | Retried as TARGET_RATE_LIMITED. |
transient | A temporary condition; the same request succeeds later. | Retried as TARGET_UNAVAILABLE. |
diagnosis is present only when the model is confident, and field only when the error clearly points at a field that was sent, so treat both as optional. Only confident diagnoses change the outcome; a less certain one only shapes the hint, and an unclear response gets the plain rejection. New causes may be added.
Duplicates of klanex's own attempt. If an attempt timed out or got a 5xx after sending the request, the target may have performed the action anyway. When the retry is then rejected as already_exists, the execution is SUCCEEDED: result holds the rejection response and result.note explains why it counted.
What the model sees
The model checks use TypeSafe's Jev model and send only the HTTP method, target host, status code, the first 8 KB of the response body, and your payload's field names (such as customer.email or items[0].price). Payload values, headers and the full URL are never sent. A payload that uses data as object keys, such as a map keyed by email address, exposes those keys as field names.
Token vault (connections)
Store credentials once, reference them by ID — agents never see raw secrets, and OAuth tokens refresh automatically.
POST /v1/connections create
GET /v1/connections list (never returns secrets)
DELETE /v1/connections/{id} remove
GET /v1/connections/{id}/authorize begin OAuth consent (browser)
- static_bearer — bring your own long-lived token.
- oauth2_client_credentials — machine-to-machine; klanex fetches and refreshes.
- oauth2_authorization_code — browser consent; presets for GitHub, Google, Slack, HubSpot, Salesforce, Stripe.
Then submit with "target": {"url": "…", "connection_id": "con_…"} — the worker injects a fresh token at execution time.
Slack & Jira integrations
Slack — approval buttons + failure alerts
- Create a Slack app (
api.slack.com/apps→ From scratch). - Incoming Webhooks → activate → add to a channel → copy the
hooks.slack.comURL. - Basic Information → copy the Signing Secret.
- Interactivity & Shortcuts → ON → Request URL:
https://api.klanexai.com/integrations/slack/actions
curl -X PUT https://api.klanexai.com/v1/integrations/slack \
-H "X-API-Key: klx_…" -H "Content-Type: application/json" \
-d '{"webhook_url":"https://hooks.slack.com/services/…",
"signing_secret":"…","notify_on_failure":true}'
Gated executions post Approve/Reject buttons; clicks are verified with Slack's request signature and recorded in the audit trail. Payload contents never appear in Slack.
Jira — auto-file issues on failures
curl -X PUT https://api.klanexai.com/v1/integrations/jira \
-H "X-API-Key: klx_…" -H "Content-Type: application/json" \
-d '{"base_url":"https://yourco.atlassian.net","email":"you@yourco.com",
"api_token":"…","project_key":"OPS","issue_type":"Task"}'
Every terminally failed execution files an issue with the error and replay instructions — never the payload. Check state with GET /v1/integrations; remove with DELETE /v1/integrations/slack or …/jira.
Credential rotation
POST /v1/api-key/rotate — new key returned once; the old key stops working immediately.
POST /v1/webhook-secret/rotate — callbacks after rotation are signed with the new secret; update your verifier.
Both are also one click in the dashboard under Credentials.
MCP server
Agents can drive klanex over the Model Context Protocol instead of raw REST. The /mcp endpoint (Streamable HTTP) lives on the same hosts as the API:
| Endpoint | Environment | Keys |
|---|---|---|
https://api.klanexai.com/mcp | production | klx_live_… |
https://api.sandbox.klanexai.com/mcp | sandbox (free) | klx_test_… |
Authenticate with the same API key, sent as either an X-API-Key header or Authorization: Bearer klx_… (for clients that can only set an Authorization header). Add it to Claude Code:
claude mcp add --transport http klanex https://api.klanexai.com/mcp \
--header "X-API-Key: klx_live_…"
For stdio-only clients (e.g. Claude Desktop), use the npm shim — it proxies to the hosted endpoint and routes klx_test_… keys to the sandbox automatically:
{
"mcpServers": {
"klanex": {
"command": "npx",
"args": ["-y", "klanex-mcp"],
"env": { "KLANEX_API_KEY": "klx_live_…" }
}
}
}
Six tools are exposed. They proxy to the REST endpoints above in-process, so validation, the schema gate, quota, rate limits, idempotency, and credential sealing behave identically — and errors surface as tool errors carrying the same llm_hint, so the agent self-corrects.
| Tool | Does |
|---|---|
execute | Submit an intent; optional wait_seconds (≤55) blocks for the terminal result. |
get_execution | Status, attempts, result, or classified error for one execution. |
list_executions | The account's executions, filterable by status and time, paginated. |
replay_execution | Re-run a terminal execution byte-exact — outage recovery without re-prompting. |
get_usage | Plan and current-month usage/quota. |
list_connections | Stored credential connections to reference via connection_id. |
SDKs
- TypeScript / Node:
npm install klanex-sdk(npm, GitHub). Zero-dependency client, webhook verification, key rotation, plus adapters for the Vercel AI SDK (klanex-sdk/ai) and the OpenAI Agents SDK (klanex-sdk/openai-agents). - Python:
pip install klanex(PyPI, GitHub). Sync and async clients, webhook verification, plusklanex.adaptersfor LangGraph / LangChain, the OpenAI Agents SDK, CrewAI, and Google ADK.
Agent framework adapters
An adapter turns one API call into a native tool. The model's tool input becomes the request payload, and the model never sees the URL or credentials. The tool returns text the model can act on:
| Outcome | The tool returns |
|---|---|
| Succeeded | The target's response. |
| Rejected, or failed the schema gate | The llm_hint, naming the bad field when klanex can tell. |
| Waiting for approval, or still running after the wait timeout (default 2 minutes) | A note that the action is in progress and the tool must not be called again for it. |
| Bad API key, quota, or network error | Nothing: the error is raised for the framework to surface to you. |
The framework's tool call ID becomes the idempotency_key, so a resumed or retried step never runs the action twice (turn it off with idempotency: false / idempotency=False). A plain JSON Schema is also sent as payload_schema, because these frameworks pass JSON Schema input through without validating it.
// Vercel AI SDK (ai 5, 6, or 7)
import { klanexTool } from "klanex-sdk/ai";
const refund = klanexTool(klanex, {
description: "Refund a Stripe charge",
inputSchema: z.object({ charge: z.string(), amount: z.number().int() }),
target: { url: "https://api.stripe.com/v1/refunds", connectionId: "con_…" },
});
await generateText({ model, tools: { refund }, stopWhen: stepCountIs(5), prompt });
// OpenAI Agents SDK
import { klanexTool } from "klanex-sdk/openai-agents";
const refund = klanexTool(klanex, { name: "create_refund", description, parameters, target });
# LangGraph / LangChain: pip install "klanex[langgraph]"
from klanex.adapters import langchain_tool
refund = langchain_tool(klanex, name="create_refund", description="Refund a Stripe charge",
target={"url": "https://api.stripe.com/v1/refunds", "connection_id": "con_…"},
payload_schema=refund_schema)
agent = create_agent(model, tools=[refund])
# OpenAI Agents SDK (Python): pip install "klanex[openai-agents]"
from klanex.adapters import openai_agents_tool
refund = openai_agents_tool(klanex, name="create_refund", description=..., target=..., payload_schema=...)
Or plain HTTP from anything else — the whole surface is in openapi.yaml.