Examples
curl, Node and Python examples for common tasks.
# Render and save a PNG
Render a small funnel and write the PNG to disk.
curl https://sankeykit.com/api/v1/render \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"flows": [
{ "source": "Visitors", "target": "Signups", "value": 120 },
{ "source": "Signups", "target": "Paid", "value": 30 }
],
"options": { "format": "png" }
}' \
-o chart.pngimport { writeFile } from 'node:fs/promises';
const res = await fetch('https://sankeykit.com/api/v1/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SANKEYKIT_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
flows: [
{ source: 'Visitors', target: 'Signups', value: 120 },
{ source: 'Signups', target: 'Paid', value: 30 }
],
options: { format: 'png' }
})
});
if (!res.ok) {
const body = await res.json();
throw new Error(`Render failed (${res.status}): ${body.error ?? body.message}`);
}
await writeFile('chart.png', Buffer.from(await res.arrayBuffer()));
console.log('Saved chart.png');import os
import requests
res = requests.post(
"https://sankeykit.com/api/v1/render",
headers={"Authorization": f"Bearer {os.environ['SANKEYKIT_API_KEY']}"},
json={
"flows": [
{"source": "Visitors", "target": "Signups", "value": 120},
{"source": "Signups", "target": "Paid", "value": 30},
],
"options": {"format": "png"},
},
)
if not res.ok:
body = res.json()
raise RuntimeError(f"Render failed ({res.status_code}): {body.get('error') or body.get('message')}")
with open("chart.png", "wb") as f:
f.write(res.content)
print("Saved chart.png")# Handling 429 and retrying
Read the Retry-After header (seconds) and wait before retrying, instead of hammering the
endpoint.
# -i shows headers so you can read Retry-After on a 429
curl -i https://sankeykit.com/api/v1/render \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "flows": [{ "source": "a", "target": "b", "value": 1 }] }'async function renderWithRetry(body, attempt = 0) {
const res = await fetch('https://sankeykit.com/api/v1/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SANKEYKIT_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (res.status === 429 && attempt < 3) {
const retryAfter = Number(res.headers.get('retry-after') ?? 30);
console.log(`Quota exceeded, retrying in ${retryAfter}s`);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return renderWithRetry(body, attempt + 1);
}
return res;
}import time
import requests
def render_with_retry(body, attempt=0):
res = requests.post(
"https://sankeykit.com/api/v1/render",
headers={"Authorization": f"Bearer {API_KEY}"},
json=body,
)
if res.status_code == 429 and attempt < 3:
retry_after = int(res.headers.get("retry-after", 30))
print(f"Quota exceeded, retrying in {retry_after}s")
time.sleep(retry_after)
return render_with_retry(body, attempt + 1)
return res# Reading remaining quota
Every response — success or error — includes the X-RateLimit-* headers.
curl -s -D - https://sankeykit.com/api/v1/render \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "flows": [{ "source": "a", "target": "b", "value": 1 }] }' \
-o /dev/null | grep -i x-ratelimitconst res = await fetch('https://sankeykit.com/api/v1/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SANKEYKIT_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ flows: [{ source: 'a', target: 'b', value: 1 }] })
});
const remaining = res.headers.get('x-ratelimit-remaining');
const limit = res.headers.get('x-ratelimit-limit');
const resetsAt = new Date(Number(res.headers.get('x-ratelimit-reset')) * 1000);
console.log(`${remaining}/${limit} renders left, resets ${resetsAt.toISOString()}`);import os
import requests
res = requests.post(
"https://sankeykit.com/api/v1/render",
headers={"Authorization": f"Bearer {os.environ['SANKEYKIT_API_KEY']}"},
json={"flows": [{"source": "a", "target": "b", "value": 1}]},
)
remaining = res.headers.get("x-ratelimit-remaining")
limit = res.headers.get("x-ratelimit-limit")
resets_at = res.headers.get("x-ratelimit-reset")
print(f"{remaining}/{limit} renders left, resets at {resets_at}")