The shape of it
Channekt has two programmatic surfaces, and they are for different callers.
-
/mcp- the Model Context Protocol server. This is the one to use from an AI assistant, and the one this reference is mostly about. It is JSON-RPC 2.0 over Streamable HTTP, authenticated by a connection token you create in the product. See AI assistants and MCP for the tool catalogue. -
/v1/*- the REST surface the Channekt web application itself uses. It is authenticated by a browser session and is documented here for the routes that manage MCP connections. A general-purpose REST API with its own credentials is not offered yet; if you want one, write to support@channekt.com and say what you would use it for.
Base address: https://api.channekt.com. Everything is
JSON, everything is over TLS, and every request may carry an
X-Request-Id header holding a UUID, which comes back on the response
and appears in our logs.
Authentication
The MCP endpoint takes a connection token, created under Settings › AI connections in Channekt:
Authorization: Bearer ck_mcp_<your token>
A token belongs to one workspace and to one person, carries an explicit list of permissions, expires (ninety days by default, at most a year), and can be revoked on its own without affecting anybody's sign-in. It is shown once at creation; Channekt stores only its SHA-256.
Everything the token can do is a subset of what its owner can do. Removing that person from the workspace, or changing their role, takes effect on the connection's very next request - the membership is read every time rather than trusted from the token.
A request with no token, a token that is not ours, a revoked one or an expired one all get the same answer:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Channekt MCP"
{
"jsonrpc": "2.0",
"id": null,
"error": {
"code": -32002,
"message": "That credential is not valid for Channekt. Create a connection under Settings, AI connections, and use the token it shows you once."
}
}
The cases are deliberately indistinguishable from outside. Which one it was is on the activity list in Channekt, where you are signed in.
The /v1/* routes take a short-lived access token issued by signing in,
in the same Authorization: Bearer header. They are the web
application's own surface; a connection token does not work on them, and an access
token does not work on /mcp.
The MCP endpoint
POST /mcp takes one JSON-RPC 2.0 message, or an array of them, and
answers with one response object, an array of them, or 202 with no
body when every message was a notification.
GET /mcp answers 405: this server sends no
server-initiated messages, so there is no stream to open.
DELETE /mcp answers 204 and does nothing - there is no
session to end, because every request authenticates on its own.
Supported protocol versions, newest first:
2025-06-182025-03-262024-11-05
A client naming a version we do not speak is answered with our newest rather than refused, which is what the specification prescribes.
Initialising:
POST /mcp
Authorization: Bearer ck_mcp_<your token>
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": { "protocolVersion": "2025-06-18" }
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": false } },
"serverInfo": { "name": "channekt-mcp", "title": "Channekt", "version": "1a2b3c4" }
}
}
tools/list returns only the tools this connection was
granted. A tool it cannot use is not in the list, so a model is never offered
something it will be refused for.
Calling a tool:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_inventory_discrepancies",
"arguments": { "limit": 25 }
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "{ ... }" }],
"structuredContent": {
"discrepancies": [
{
"listingId": "018f4b1a-0000-7000-8000-00000000a001",
"channel": "MARKETPLACE",
"shop": "Demo Trading (Marketplace)",
"sku": "DEMO-TSHIRT-M",
"warehouseCode": "MAIN",
"expected": 12,
"reported": 4,
"difference": 8,
"lastPushedAt": "2026-09-03T18:22:11Z",
"syncState": "IN_SYNC",
"lastError": null
}
],
"nextCursor": null,
"limit": 25,
"advertisingMoreThanWeHold": 0
}
}
}
The answer appears twice on purpose: structuredContent for clients
that read it, and the same object serialised in content for clients
that do not. Every identifier in these examples is fictional.
Carrying out a change is always two calls:
{ "method": "tools/call",
"params": { "name": "propose_price_update",
"arguments": { "skus": ["DEMO-TSHIRT-M"], "changeByPercent": -10 } } }
{
"actionId": "018f4b1a-0000-7000-8000-00000000b001",
"kind": "price_update",
"preview": "Change 1 price:\n DEMO-TSHIRT-M: 19.99 -> 17.99 GBP",
"warnings": [],
"approvalRequired": true,
"approvalReason": "This workspace asks a person to agree to every change an assistant proposes.",
"status": "PROPOSED",
"expiresAt": "2026-09-04T13:00:00Z"
}
After the merchant has approved it in Channekt, the assistant calls
execute_price_update with the actionId and its own
idempotencyKey. No other argument is accepted - what
runs is the stored plan, revalidated against the state of the moment. A repeated
execute with the same key returns the first result rather than doing the work
twice.
Managing connections
These are /v1/* routes, reached with a signed-in session, and every
one of them needs the apikey:manage permission - which owners and
administrators hold.
| Route | What it does |
|---|---|
GET /v1/mcp/scopes |
The permissions that can be granted, each with a sentence. |
GET /v1/mcp/connections |
This workspace's connections. Never the tokens. |
POST /v1/mcp/connections |
Mints one. The only response that carries a token. |
POST /v1/mcp/connections/{id}/revoke |
Stops it on its next request. |
POST /v1/mcp/connections/{id}/rotate |
Issues a replacement and revokes the old one at the same moment. |
GET /v1/mcp/activity |
What connections have been asked for, allowed or refused. |
GET /v1/mcp/policy |
How far assistants may go in this workspace. Needs org:read. |
PUT /v1/mcp/policy |
Changes it. |
GET /v1/mcp/actions/pending |
Proposals waiting for somebody to agree. |
POST /v1/mcp/actions/{id}/approve |
Agrees to one. Not reachable from an assistant. |
POST /v1/mcp/actions/{id}/reject |
Declines one. |
POST /v1/mcp/connections
{
"name": "Claude on my laptop",
"scopes": ["mcp:orders:read", "mcp:inventory:read"],
"lifetimeDays": 90
}
201 Created
{
"connection": {
"id": "018f4b1a-0000-7000-8000-00000000c001",
"name": "Claude on my laptop",
"tokenPrefix": "AbCdEf12",
"scopes": ["mcp:inventory:read", "mcp:orders:read"],
"effectivePermissions": ["inventory:read", "order:read"],
"canAct": false,
"createdAt": "2026-09-04T10:00:00Z",
"expiresAt": "2026-12-03T10:00:00Z",
"lastUsedAt": null,
"revokedAt": null,
"status": "ACTIVE"
},
"token": "ck_mcp_EXAMPLE_ONLY_THIS_IS_NOT_A_REAL_TOKEN"
}
Errors
The MCP endpoint answers in JSON-RPC:
| Code | Meaning |
|---|---|
-32700 | The body is not JSON. |
-32600 | Not a JSON-RPC 2.0 request. |
-32601 | No such method, or no such tool. |
-32602 |
The arguments are wrong. The message names the field. |
-32603 |
A failure, recorded, having changed nothing. |
-32001 | The request was cancelled or timed out. |
-32002 |
Not authenticated, or the connection was not granted this. |
An error message is written to be acted on and never carries an internal detail: no
database identifiers, no stack traces, no marketplace responses, no customer data.
A tool that refuses for a reason the model should read - an unknown SKU, a proposal
nobody has approved - answers with isError and a message rather than
with a protocol error.
The /v1/* routes answer in RFC 9457 Problem Details, with a
reference you can quote to support.
Rate limits and timeouts
Requests are limited per address. The general allowance is 300 a minute; exceeding
it answers 429 with the number of seconds to wait. One tool call has
thirty seconds to finish; a client that gives up before then has its work abandoned,
and anything already queued stays queued.
A message body is capped at 256 KB, which is far more than a tool call needs.
Paging
Every list is bounded at a hundred rows and returns nextCursor, or
null when there is no more. Pass the cursor back unchanged; never
construct one. The limit you asked for is echoed back, so a request
that was clamped is visible rather than silent.
A few tools page by number instead, and return page,
pageCount and total. Report the total rather than counting
the rows you were given.
Money, dates and units
An amount is an object, not a number:
{ "minor": 1799, "currency": "GBP", "decimal": "17.99" }. Compute with
minor; show decimal. When a channel charged in a different
currency, originalMinor and originalCurrency carry what it
actually charged - their absence is the statement that no conversion happened.
Dates are ISO 8601 in UTC. Weights are grams, dimensions are millimetres, and
quantities are whole numbers. available is what can be sold - on hand
less what is promised to orders - and it is the figure to quote.
Webhooks
Channekt receives webhooks from the channels you connect; it does not yet send them
to you. An assistant that needs to know how a queued operation ended polls
get_channel_status or list_sync_errors. Outbound webhooks
are not built, and this page will say so until they are.
Versioning
The MCP protocol version is negotiated per connection. The tool catalogue itself is additive: tools and fields are added, and an existing tool's arguments are not removed or renamed without a new name beside the old one. A change that would break a caller gets a new tool.
The REST surface is versioned in its path. /v1 is what exists.
The serverInfo.version returned by initialize is the build
you are talking to, which is what to quote when reporting something.