Preview. This site is still being built. Features, prices and terms may change, and nothing here is a binding offer. Read the preview notice

Preview. Still being built; nothing here is a binding offer. Details

Requests, errors and limits

How every route answers, what errors look like and how to retry safely.

Base URL and version

Call https://api.intersink.com/v1. The version is in the path, and every response carries X-Kodkila-Version: 1. A version the server doesn’t serve answers 404 with UNKNOWN_API_VERSION. Each route also answers under the older /api prefix, which is what the SDK and the OpenAPI document’s path keys use; new code should call /v1.

Requests and responses are JSON over HTTPS. Uploads use multipart/form-data, and large media uses resumable uploads.

Successful answers

The payload is in data. Lists also carry meta for paging:

GET /products?limit=2
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "data": [],
    "meta": { "total": 0, "page": 1, "limit": 2, "has_more": false }
  },
  "timestamp": "2026-11-01T12:46:49.315Z"
}

Ask for the next page while has_more is true. Search has its own deeper paging with search_after.

Errors

GET /products?limit=200
{
  "success": false,
  "statusCode": 400,
  "message": "limit must not be greater than 100",
  "errorCode": "VALIDATION_FAILED",
  "errors": ["limit must not be greater than 100"],
  "path": "/v1/products?limit=200",
  "requestId": "182d98bc-e4f2-4208-8c8e-aedd01aa2b17"
}

Branch on errorCode, never on message. Show errors next to the fields they name. Keep requestId in your logs: support can find the request with it.

StatusUsually means
400The input failed validation (VALIDATION_FAILED), or a header such as an idempotency key is malformed
401No session or it expired (UNAUTHENTICATED, TOKEN_INVALID): refresh once. A wrong code is INVALID_CODE and is not a refresh case.
403Not allowed: a missing scope, a test key trying to write, or another channel
404Not found, or not visible to you
409A conflict, such as a version mismatch or the same request still running
429Too many requests: wait as long as Retry-After says
5xxOur side. Retry later with backoff; the message is safe to show

Rate limits

Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Requests are counted per key first, then per signed-in user, then per client IP; every visitor of an app shares the app key’s count. Sign-in code routes have tighter limits of their own.

On a 429, read Retry-After (also retry_after in the body), wait at least that long, then retry with exponential backoff and jitter. Cache what you read, debounce search boxes, and never call the API in a loop.

Retrying writes safely

Orders, checkout and payments accept an Idempotency-Key header. Create one key per user action, such as one click on Pay, and send the same key and body on every retry of that action:

With the SDK
const key = crypto.randomUUID(); // once, when the user clicks Pay

await api.post("/api/orders", {
  body: { items: [{ product_id, quantity: 1 }], payment_method: "COD" },
  idempotencyKey: key, // reuse it on every retry of this order
});
When you retryYou get
After the first request finishedThe first answer again, with Idempotent-Replayed: true. Nothing runs twice.
While the first is still running409. Wait a second or two and retry with the same key.
With the same key but a different body422. Make a new key for a new action.
After the first one failedThe request runs again. Failures are not stored.

Keys are 8 to 128 characters of letters, digits, _ and -, and are remembered for 24 hours.

Conditional writes

Per-user app data uses versions: send If-Match with the version you read. On a conflict, read again, merge and retry once.