API Guide
API Guide
This guide covers the shared request lifecycle, error envelope, and cross-cutting features that apply to every endpoint on the platform. Endpoint-specific input and output shapes live on each endpoint's dedicated page.
Base URL
All API requests should be made to:
https://api.skael.de
Authentication
All requests require an API key. See the Authentication Guide for how to create and use keys.
Authorization: Bearer eak_your_api_key_here
Request Lifecycle
The platform exposes three request modes. Individual endpoints document which modes they support — most asynchronous endpoints support all three; simple read-only endpoints typically support only synchronous mode.
Synchronous Mode (Default)
When async is not set or is false, the API waits for the operation to complete before responding. This is the simplest mode for one-off requests.
Timeout: 3600 seconds (1 hour)
Response (Success - 200 OK)
{
"data": {
"id": 123,
"type": "...",
"status": "completed",
"input": { "...": "..." },
"output": { "...": "..." },
"custom_vars": { "lead_id": "12345" },
"error_message": null,
"started_at": "2026-01-15T10:30:00.000000Z",
"completed_at": "2026-01-15T10:30:05.000000Z"
}
}
Response (Timeout - 202 Accepted)
If the operation does not complete within the timeout period:
{
"data": {
"id": 123,
"status": "timeout",
"message": "Request is still processing. Check back later.",
"input": { "...": "..." },
"custom_vars": { "lead_id": "12345" }
}
}
When a timeout occurs, use the resource's GET endpoint with long-polling to check for completion.
Async Polling Mode
Set async=true to immediately receive a response with the resource ID, then poll for the result.
Step 1: Create the Resource
Your POST returns a 202 Accepted response with an ID:
{
"data": {
"id": 123,
"status": "queued",
"input": { "...": "..." },
"callback_url": null,
"custom_vars": { "lead_id": "12345" },
"queued_at": "2026-01-15T10:30:00.000000Z"
}
}
Step 2: Poll for Completion
Use the ID to poll for the result with long-polling:
GET /api/<resource>/{id}?wait=true&wait_time=50
The server waits up to wait_time seconds before responding.
wait (boolean, default: false) — Enable long-polling.
wait_time (integer, 1-3600, default: 50) — Seconds to wait before returning a 202.
A completed operation returns 200 OK with the final payload. A still-processing operation returns 202 Accepted; retry the same request.
Integration tip: For Clay.com integrations, the default wait_time=50 is optimised for Clay's retry behaviour. Clay automatically retries 202 responses.
Async Webhook Mode
Set async=true and provide a callback_url to receive a webhook notification when the operation completes.
When the operation completes (success or failure), a POST request is sent to your callback_url containing the full resource payload.
Webhook behaviour:
- Timeout: 10 seconds
- Redirects: Not followed
- SSL verification: Enabled
- The
callback_url must resolve to a public IP address (not localhost or private ranges)
Rate Limits
Authenticated requests are rate-limited per organization:
- 10,000 requests per minute per organization for standard endpoints
- 3-5 requests per minute for auth endpoints
When you exceed the limit, the API returns 429 Too Many Requests:
{
"message": "Too many requests. Please slow down."
}
When polling a single resource, only one poll request per resource is allowed at a time:
{
"message": "Another polling request is already active for this resource. Please wait."
}
Best practice: Implement exponential backoff — when you receive a 429, double the wait time with each consecutive 429.
Error Envelope
The API returns machine-readable errors in a consistent shape.
Validation Error (422 Unprocessable Entity)
Returned when input validation fails. Contains a top-level message and an errors object keyed by field name:
{
"message": "The domain field is required.",
"errors": {
"domain": ["The domain field is required."]
}
}
Failed Operation (422 Unprocessable Entity)
Returned when a resource has status=failed. The response body contains the full resource (not a validation errors object):
{
"data": {
"id": 123,
"status": "failed",
"input": { "...": "..." },
"output": null,
"error_message": "Target could not be reached",
"credits_cost": 0,
"created_at": "2026-01-15T10:30:00.000000Z",
"started_at": "2026-01-15T10:30:01.000000Z",
"completed_at": "2026-01-15T10:30:02.000000Z"
}
}
Distinguishing the two 422s: A validation error has an errors object with field names as keys. A failed operation has a data object with "status": "failed" and an error_message string.
Note: When a sync request or long-poll wait is already streaming (heartbeat architecture), the HTTP status is always 200 because headers are already sent. The 422 only applies when the failed status is known before the response begins.
Not Found (404)
{ "message": "Resource not found" }
Authentication Errors
| Status |
Message |
Description |
| 401 |
Unauthenticated. |
No API key provided or key does not start with eak_ |
| 401 |
Invalid API key. |
The provided API key is not valid |
| 429 |
Too many authentication attempts. Please try again later. |
Too many failed authentication attempts |
Status Values
Asynchronous resources move through the following states:
| Status |
Description |
queued |
Operation is queued for processing |
processing |
Operation is currently being processed |
waiting |
Operation is waiting for an external provider callback |
completed |
Operation finished successfully |
failed |
Operation failed with an error (HTTP 422 when returned directly, HTTP 200 when streamed) |
timeout |
Synchronous request timed out (operation may still complete) |
Custom Variables
The custom_vars parameter attaches customer-provided metadata to a request. This data flows through the entire system unchanged: it is stored with the resource, returned in every response (sync, async, timeout, polling), and included in webhook callbacks.
Constraints:
- Maximum nesting depth: 10 levels
- Maximum keys: 50
- Maximum size: 2 KB
- Maximum string value length: 500 characters
- Key format: alphanumeric and underscore only
- Immutable after creation
Use case: Zapier and Make.com flows can resume directly from a webhook without another lookup by including tracking IDs, campaign names, or lead data in custom_vars. The external_id convention (custom_vars.external_id) is used throughout the platform for cross-system deduplication.
Deduplication
For idempotent enrichment endpoints, the platform deduplicates identical requests within a 20-minute window scoped to the organization. Deduplication uses the organization, endpoint type, and validated input within a 20-minute window.
The validated input is the endpoint-specific payload after validation. async, callback_url, and custom_vars are excluded from the deduplication key. custom_vars are stored and returned with the resource, but they are not part of deduplication.
- A duplicate request within the window returns the existing resource rather than creating a new one.
- Deduplication applies to both succeeded and failed resources — a cached failure is returned to the caller with
status=failed and the original error_message.
- Changing
custom_vars does not force a fresh enrichment. To force a fresh request, change an endpoint input field that participates in validation, or retry after the deduplication window expires.
Endpoints that are inherently non-idempotent (for example, the destructive Create Prospect endpoint) document their own deduplication behaviour.