Your AI Employee inside your platform

Your Customer Service AI Employee can answer your customers inside your own app, website or support tool. Your platform sends the customer's message; the reply comes back in the same response, written with the same tools the AI Employee uses in WhatsApp: quotes, orders, bookings, catalog, knowledge base and escalation to a person.

The SpaceKru API (v1.1) is a REST/JSON API available on every paid SpaceKru plan. It syncs leads both ways (upsert by external_id), adds leads to campaigns, creates and moves deals and asks AI Employees to follow up, manages products, quotes, orders, payments and bookings, lets your platform chat with your Customer Service AI Employee, and sends signed webhooks for 16 events that say who caused each change. Base URL: https://app.spacekru.com/api/public/v1. Authentication: Bearer API key (sk_live_…). Documentation: https://www.spacekru.com/developers (Spanish: https://www.spacekru.com/es/developers). OpenAPI 3.1: https://app.spacekru.com/api/public/v1/openapi.json.

Leer en español

Requirements

  • An API key with Read & write and AI conversations (the account owner ticks it when creating the key).
  • In SpaceKru, an inbound campaign that includes the Web Chat channel, assigned to a Customer Service AI Employee that has the Web Chat channel turned on. API conversations are served by that campaign, exactly like the website chat.
  • AI tokens in the plan or the wallet: each reply uses them, like any other reply of your AI Employees.

Check it at any time:

curl
curl "https://app.spacekru.com/api/public/v1/conversations/availability" \
  -H "Authorization: Bearer $SPACEKRU_API_KEY"
Node.js
const res = await fetch('https://app.spacekru.com/api/public/v1/conversations/availability', {
  headers: {
    Authorization: `Bearer ${process.env.SPACEKRU_API_KEY}`,
  },
});
const body = await res.json();
if (!res.ok) {
  // Branch on body.error.code; quote body.error.request_id to support.
  throw new Error(`${body.error.code}: ${body.error.message} (request ${body.error.request_id})`);
}
console.log(body);
Python
import os

import requests

r = requests.get(
    "https://app.spacekru.com/api/public/v1/conversations/availability",
    headers={
        "Authorization": f"Bearer {os.environ['SPACEKRU_API_KEY']}",
    },
    timeout=30,
)
body = r.json()
if not r.ok:
    # Branch on body["error"]["code"]; quote body["error"]["request_id"] to support.
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} (request {body['error']['request_id']})")
print(body)
Response 200
{
  "object": "conversation_availability",
  "available": true,
  "reason": null,
  "agent": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5e",
    "name": "Sofia",
    "language": "es"
  },
  "campaign": {
    "id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
    "name": "Customer service",
    "status": "running"
  }
}

Lifecycle

POST /conversations → POST /conversations/{id}/messages → … as many turns as needed … → POST /conversations/{id}/close

1. Open a conversation

For an existing lead (lead_id) or a new one (lead, same fields as creating a lead; if it matches an existing lead by email or phone, that lead is used). Send your own id as external_conversation_id (a ticket or chat id): opening it again returns the same conversation, so retries are safe.

curl
curl -X POST "https://app.spacekru.com/api/public/v1/conversations" \
  -H "Authorization: Bearer $SPACEKRU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: your-unique-request-id" \
  -d '{
  "lead_id": "3f2b8c1e-9a4d-4e6f-b7a8-1c2d3e4f5a6b",
  "external_conversation_id": "ticket-5521",
  "locale": "es"
}'
Node.js
const res = await fetch('https://app.spacekru.com/api/public/v1/conversations', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SPACEKRU_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'your-unique-request-id',
  },
  body: JSON.stringify({
    "lead_id": "3f2b8c1e-9a4d-4e6f-b7a8-1c2d3e4f5a6b",
    "external_conversation_id": "ticket-5521",
    "locale": "es"
  }),
});
const body = await res.json();
if (!res.ok) {
  // Branch on body.error.code; quote body.error.request_id to support.
  throw new Error(`${body.error.code}: ${body.error.message} (request ${body.error.request_id})`);
}
console.log(body);
Python
import os

import requests

r = requests.post(
    "https://app.spacekru.com/api/public/v1/conversations",
    headers={
        "Authorization": f"Bearer {os.environ['SPACEKRU_API_KEY']}",
        "Idempotency-Key": "your-unique-request-id",
    },
    json={
        "lead_id": "3f2b8c1e-9a4d-4e6f-b7a8-1c2d3e4f5a6b",
        "external_conversation_id": "ticket-5521",
        "locale": "es",
    },
    timeout=30,
)
body = r.json()
if not r.ok:
    # Branch on body["error"]["code"]; quote body["error"]["request_id"] to support.
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} (request {body['error']['request_id']})")
print(body)

2. Send the customer's messages

Each call sends one message (up to 4000 characters) and waits for the AI Employee. Most replies take a few seconds; with tools (quoting, booking) it can take up to about a minute. Set your HTTP timeout to at least 90 seconds.

curl
curl -X POST "https://app.spacekru.com/api/public/v1/conversations/18293a4b-5c6d-4e7f-8091-a2b3c4d5e6f7/messages" \
  -H "Authorization: Bearer $SPACEKRU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Hi, do you have a table for 4 on Saturday at 8pm?"
}'
Node.js
const res = await fetch('https://app.spacekru.com/api/public/v1/conversations/18293a4b-5c6d-4e7f-8091-a2b3c4d5e6f7/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SPACEKRU_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "text": "Hi, do you have a table for 4 on Saturday at 8pm?"
  }),
});
const body = await res.json();
if (!res.ok) {
  // Branch on body.error.code; quote body.error.request_id to support.
  throw new Error(`${body.error.code}: ${body.error.message} (request ${body.error.request_id})`);
}
console.log(body);
Python
import os

import requests

r = requests.post(
    "https://app.spacekru.com/api/public/v1/conversations/18293a4b-5c6d-4e7f-8091-a2b3c4d5e6f7/messages",
    headers={
        "Authorization": f"Bearer {os.environ['SPACEKRU_API_KEY']}",
    },
    json={
        "text": "Hi, do you have a table for 4 on Saturday at 8pm?",
    },
    timeout=30,
)
body = r.json()
if not r.ok:
    # Branch on body["error"]["code"]; quote body["error"]["request_id"] to support.
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} (request {body['error']['request_id']})")
print(body)
Response 200
{
  "object": "conversation_turn",
  "conversation_id": "18293a4b-5c6d-4e7f-8091-a2b3c4d5e6f7",
  "messages": [
    {
      "object": "conversation_message",
      "id": "29304b5c-6d7e-4f80-91a2-b3c4d5e6f708",
      "conversation_id": "18293a4b-5c6d-4e7f-8091-a2b3c4d5e6f7",
      "role": "agent",
      "text": "Hi Maria! Yes, Table 4 on the terrace is free on Saturday at 8pm. Shall I book it for you?",
      "created_at": "2026-10-20T15:00:06.000Z"
    }
  ],
  "deferred": false,
  "deferred_reason": null
}

The AI Employee can answer with more than one message: show every item of messages in order.

3. Close it

Conversations do not expire. Close one when the case ends (POST /conversations/{id}/close); a closed conversation rejects new messages with 422. Open a new one to talk again.

When there is no reply

The customer's message is always saved in the SpaceKru Inbox. When the AI Employee does not answer right away, deferred is true and deferred_reason says why:

deferred_reasonMeaningWhat to do
replies_disabledA person took over this customer, or AI replies were turned off for them.Wait for the person: their message arrives by webhook.
campaign_inactiveThe inbound campaign is paused. A supervisor is alerted.Resume the campaign in SpaceKru.
message_filteredThe message was filtered (spam, opt-out, auto-reply…).Nothing.
no_inbound_campaign / agent_not_readyThe setup changed after the conversation was opened.Check GET /conversations/availability.
no_replyThe AI Employee decided not to write anything this time.Nothing.

A person can take over

Every API conversation appears in the SpaceKru Inbox as Web Chat. Anyone on the team can answer from there. Every message written to the customer — by a person or by the AI Employee, including the replies you already received in the turn response — fires the conversation.message_created webhook (data.role is human for a person and agent for the AI Employee; deduplicate by data.id). Subscribe to it to never miss a reply:

curl
curl -X POST "https://app.spacekru.com/api/public/v1/subscriptions" \
  -H "Authorization: Bearer $SPACEKRU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "conversation.message_created",
  "target_url": "https://your-app.example.com/hooks/spacekru"
}'
Node.js
const res = await fetch('https://app.spacekru.com/api/public/v1/subscriptions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SPACEKRU_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "event_type": "conversation.message_created",
    "target_url": "https://your-app.example.com/hooks/spacekru"
  }),
});
const body = await res.json();
if (!res.ok) {
  // Branch on body.error.code; quote body.error.request_id to support.
  throw new Error(`${body.error.code}: ${body.error.message} (request ${body.error.request_id})`);
}
console.log(body);
Python
import os

import requests

r = requests.post(
    "https://app.spacekru.com/api/public/v1/subscriptions",
    headers={
        "Authorization": f"Bearer {os.environ['SPACEKRU_API_KEY']}",
    },
    json={
        "event_type": "conversation.message_created",
        "target_url": "https://your-app.example.com/hooks/spacekru",
    },
    timeout=30,
)
body = r.json()
if not r.ok:
    # Branch on body["error"]["code"]; quote body["error"]["request_id"] to support.
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} (request {body['error']['request_id']})")
print(body)

GET /conversations/{id}/messages returns the whole history, oldest first.

Tokens, limits and errors

SituationAnswer
No AI tokens left in the plan or the wallet402 AI_QUOTA_EXHAUSTED
The setup is incomplete409 CONVERSATIONS_NOT_CONFIGURED (details[0].issue says what is missing)
More than 4 AI replies in progress on one server503 SERVICE_BUSY with Retry-After
The model did not answer in time503 SERVICE_BUSY: the customer's message is already saved; retry later or let a person answer
The daily Web Chat message limit of the account was reached429 RATE_LIMITED
Conversations are switched off503 CONVERSATIONS_UNAVAILABLE