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

  1. Sign up — verify your email, put a card on file, and save the api_key and webhook_secret shown once.
  2. 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"}
  1. 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

FieldMeaning
target.url, target.methodThe third-party call to make. Default method POST.
target.headersCredentials for the target. Encrypted at rest (Cloud KMS); never readable again, redacted in all reads.
target.connection_idUse a vault connection instead of raw headers.
target.timeout_msPer-attempt timeout.
payloadThe JSON body your agent produced. Stored byte-exact for replay.
payload_schemaJSON Schema gate. Invalid payloads bounce with 422 + llm_hint — feed it back to your model and resubmit.
callback_urlWhere to POST the signed result webhook.
max_attemptsRetry budget.
idempotency_keySame key → same execution returned (200 + X-Klanex-Idempotent-Replay: true), never a duplicate run.
requires_approvalPause 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
}
HeaderMeaning
X-Klanex-Signaturesha256= + hex HMAC-SHA256 of "<timestamp>.<body>" keyed with your webhook_secret
X-Klanex-TimestampUnix seconds used in the signature — reject stale values to prevent replay
X-Klanex-EventEvent name
X-Klanex-Execution-IdExecution 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.

CodeTerminal?Meaning
SCHEMA_INVALIDyes (422 at submit)Payload failed your schema — fix and resubmit.
TARGET_TIMEOUTretriedTarget didn't answer in time.
TARGET_RATE_LIMITEDretriedTarget returned 429, or another rejection whose response reports a rate limit.
TARGET_UNAVAILABLEretriedTarget 5xx / connection failure, or a rejection whose response reports a temporary condition.
TARGET_REJECTEDyesTarget 4xx, or a 2xx whose body reports a failure. Retrying won't help; correct the request as the llm_hint and diagnosis say.
CIRCUIT_OPENretriedHost breaker open; klanex waits it out.
ATTEMPTS_EXHAUSTEDyesRetry budget spent.
APPROVAL_REJECTEDyesA human said no.
CONNECTION_UNAUTHORIZEDyesVault connection can't mint a valid token, or the target rejected its credential. Reauthorize it.
RATE_LIMITED429 at submitYour 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.

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" }
}
CauseMeaningWhat klanex does
invalid_payloadA field is missing, malformed, out of range, or stale (outdated version or etag).TARGET_REJECTED; the hint names the field when known.
authCredential missing, invalid, expired, or revoked. Every 401.TARGET_REJECTED, or CONNECTION_UNAUTHORIZED for a vault connection.
permissionAuthenticated but not allowed: scope, role, plan, or quota.TARGET_REJECTED; the hint says not to retry.
not_foundThe endpoint or a referenced ID or name does not exist.TARGET_REJECTED; the hint says to look the value up, not guess.
already_existsA duplicate, or the action was already performed.TARGET_REJECTED, or SUCCEEDED when klanex's own earlier attempt did it.
rate_limitedThe API asks to slow down, whatever the status code.Retried as TARGET_RATE_LIMITED.
transientA 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)

Then submit with "target": {"url": "…", "connection_id": "con_…"} — the worker injects a fresh token at execution time.

Slack & Jira integrations

Easiest path: open the dashboard → click the integrations chip → guided setup with copy-paste values. The API equivalents are below.

Slack — approval buttons + failure alerts

  1. Create a Slack app (api.slack.com/apps → From scratch).
  2. Incoming Webhooks → activate → add to a channel → copy the hooks.slack.com URL.
  3. Basic Information → copy the Signing Secret.
  4. 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:

EndpointEnvironmentKeys
https://api.klanexai.com/mcpproductionklx_live_…
https://api.sandbox.klanexai.com/mcpsandbox (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.

ToolDoes
executeSubmit an intent; optional wait_seconds (≤55) blocks for the terminal result.
get_executionStatus, attempts, result, or classified error for one execution.
list_executionsThe account's executions, filterable by status and time, paginated.
replay_executionRe-run a terminal execution byte-exact — outage recovery without re-prompting.
get_usagePlan and current-month usage/quota.
list_connectionsStored credential connections to reference via connection_id.
klanex is listed on the official MCP Registry as io.github.chrassy/klanex, on npm, and on GitHub.

SDKs

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:

OutcomeThe tool returns
SucceededThe target's response.
Rejected, or failed the schema gateThe 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 errorNothing: 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.