Rate limits

Each API key can make 120 requests per minute and 10,000 requests per day by default. Limits are per key, not per IP address, so integrations that share IP addresses (Zapier, Make, n8n) never slow each other down.

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

The limits

LimitDefaultWindow
Requests per key per minute120Fixed one-minute windows (by the clock, UTC).
Requests per key per day10,000Calendar day in UTC. Resets at 00:00 UTC.
AI conversation replies in progress4 per serverTheir own pool: a burst of conversations never blocks the rest of the API.
Request body size1 MBPer request. Larger bodies answer 413 PAYLOAD_TOO_LARGE.
Page size100 recordsPer list request (default 25).
Leads per enrollment call100POST /campaigns/{id}/leads

Need more? Use more than one key only when the integrations are really separate — and tell us about your use case through the contact page.

Rate limit headers

Every authenticated response tells you where you stand:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute for this key.
X-RateLimit-RemainingRequests left in the current minute.
X-RateLimit-ResetSeconds until the current minute resets (a duration, not a timestamp).
X-RateLimit-Daily-LimitRequests allowed per UTC day.
X-RateLimit-Daily-RemainingRequests left today.
Retry-AfterSeconds to wait. Sent with 429 and 503.
X-Request-IdId of the request. Quote it when you contact support.
Content-LanguageLanguage of error.message (en or es).
Example
HTTP/1.1 200 OK
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 41
X-RateLimit-Daily-Limit: 10000
X-RateLimit-Daily-Remaining: 9988
X-Request-Id: 5f2c9a1b7e3d

When you hit a limit: 429

  • Over the per-minute limit: 429 RATE_LIMITED with Retry-After = seconds until the next minute (at most 60).
  • Over the daily limit: 429 RATE_LIMITED with Retry-After = seconds until 00:00 UTC.
  • Requests rejected by the per-minute limit don't count against the daily limit.

When we are busy: 503

  • 503 SERVICE_BUSY: the server is handling its maximum of requests at this moment. Retry-After: 5 (15 for AI conversation replies). It protects every customer's AI Employees, which run on the same servers.
  • 503 ELIGIBILITY_UNAVAILABLE: we could not verify your plan for a moment. Retry-After: 10.
  • Both are temporary and safe to retry. Writes that failed with 503 were not applied.

Retry with backoff

Retry 429 and 503 after Retry-After seconds plus a little random jitter, so many workers don't retry at the same instant. For creates, send an Idempotency-Key so a retry can never create a duplicate (Idempotency).

Node.js
const BASE = 'https://app.spacekru.com/api/public/v1';

// Retries 429 and 503 after Retry-After (+ jitter). Gives up on long waits
// (the daily limit resets at 00:00 UTC) and after 5 retries.
export async function spacekru(path, options = {}, attempt = 0) {
  const res = await fetch(`${BASE}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${process.env.SPACEKRU_API_KEY}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if ((res.status === 429 || res.status === 503) && attempt < 5) {
    const retryAfter = Number(res.headers.get('Retry-After')) || 2 ** attempt;
    if (retryAfter > 60) throw new Error('Daily limit reached: resume after 00:00 UTC.');
    const waitMs = retryAfter * 1000 + Math.random() * 1000;
    await new Promise((resolve) => setTimeout(resolve, waitMs));
    return spacekru(path, options, attempt + 1);
  }

  const body = await res.json();
  if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (request ${body.error.request_id})`);
  return body;
}

// Usage
const page = await spacekru('/leads?limit=100');
console.log(page.data.length);
Python
import os
import random
import time

import requests

BASE = "https://app.spacekru.com/api/public/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SPACEKRU_API_KEY']}"}


def spacekru(method, path, **kwargs):
    """Retries 429 and 503 after Retry-After (+ jitter), at most 5 times."""
    for attempt in range(6):
        r = requests.request(method, BASE + path, headers=HEADERS, timeout=30, **kwargs)
        if r.status_code in (429, 503) and attempt < 5:
            retry_after = int(r.headers.get("Retry-After", 2 ** attempt))
            if retry_after > 60:
                raise RuntimeError("Daily limit reached: resume after 00:00 UTC.")
            time.sleep(retry_after + random.random())
            continue
        body = r.json()
        if not r.ok:
            err = body["error"]
            raise RuntimeError(f"{err['code']}: {err['message']} (request {err['request_id']})")
        return body


page = spacekru("GET", "/leads", params={"limit": 100})
print(len(page["data"]))

Staying well under the limits

  • Use webhooks instead of polling. One subscription to lead.created replaces thousands of GET /leads calls (Webhooks).
  • Sync incrementally with updated_since and limit=100 instead of re-reading everything (Pagination & sync).
  • Spread batch jobs over time instead of firing them all at once.