Scoped API keys with Bearer token auth. SHA-256 hashed and never stored in plaintext.
All API requests must include your API key in the Authorization header:
Authorization: Bearer ik_your_api_key_here
API keys are prefixed with ik_ for identification. Keys are hashed and never stored in plaintext.
| Scope | Description |
|---|---|
| clients:read | List and view client details |
| clients:write | Onboard new clients |
| messages:send | Send messages via the API |
| webhooks:read | View webhook config and logs |
| webhooks:write | Update webhook configuration |
| Tier | Requests/min | Webhooks/month | Clients |
|---|---|---|---|
| Free | 60 | 10,000 | 3 |
| Pro | 300 | 100,000 | 25 |
| Enterprise | 1,000 | Unlimited | Unlimited |
Every API response includes rate limit headers so you can track your usage programmatically:
X-RateLimit-Limit: 300 X-RateLimit-Remaining: 247 X-RateLimit-Reset: 1741824000 Retry-After: 60 # only present on 429 responses
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed per minute for your tier |
| X-RateLimit-Remaining | Requests remaining in the current window |
| X-RateLimit-Reset | Unix timestamp when the rate limit window resets |
| Retry-After | Seconds to wait before retrying (only on 429 Too Many Requests) |
const res = await fetch("https://api.intelliportal.io/v1/messages/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INTELLI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const remaining = parseInt(res.headers.get("X-RateLimit-Remaining") ?? "0");
const resetAt = parseInt(res.headers.get("X-RateLimit-Reset") ?? "0");
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") ?? "60");
console.log(`Rate limited. Retry in ${retryAfter}s`);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
console.log(`${remaining} requests remaining. Resets at ${new Date(resetAt * 1000)}`);import requests, time
res = requests.post(
"https://api.intelliportal.io/v1/messages/send",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json=payload,
)
remaining = int(res.headers.get("X-RateLimit-Remaining", 0))
reset_at = int(res.headers.get("X-RateLimit-Reset", 0))
if res.status_code == 429:
retry_after = int(res.headers.get("Retry-After", 60))
print(f"Rate limited. Retrying in {retry_after}s")
time.sleep(retry_after)
print(f"{remaining} requests remaining")To safely retry requests on network failures without sending duplicate messages, include an Idempotency-Key header with a unique value (we recommend a UUID v4).
POST https://api.intelliportal.io/v1/messages/send Authorization: Bearer ik_live_your_key Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 Content-Type: application/json
How it works: Idempotency keys are valid for 24 hours. If you send the same key within that window, IntelliPortal returns the cached response from the original request without re-sending the message. After 24 hours, the key expires and can be reused.
import { randomUUID } from "crypto";
const idempotencyKey = randomUUID();
const res = await fetch("https://api.intelliportal.io/v1/messages/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INTELLI_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
client_ref: "partner-123",
to: "+1234567890",
type: "text",
text: { body: "Order confirmed!" },
}),
});import uuid, requests
res = requests.post(
"https://api.intelliportal.io/v1/messages/send",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"client_ref": "partner-123",
"to": "+1234567890",
"type": "text",
"text": {"body": "Order confirmed!"},
},
)