Skip to content

Quickstart

One HTTP call gets you a hosted checkout URL that collects a donation through PSE, Nequi, Bre-B or a card. This page is that call, plus how to know it worked.

You need two things: an API key with the checkout_links:write permission, and the key of the vaki you are collecting for — the slug in its public URL. If https://vaki.co/clinicalaliga is the cause, the key is clinicalaliga. If you do not have a cause yet, create one first.

Never commit it, never put it in a query string, never ship it to a browser.

Terminal window
export VAKI_API_KEY="…the key Vaki issued you…"
Terminal window
curl https://api.vaki.co/v1/checkout_links \
-H "Authorization: Bearer $VAKI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-$(date +%s)" \
-d '{
"vaki": "clinicalaliga",
"amount": 50000,
"currency": "COP",
"email": "donante@example.com",
"external_reference": "DON_59LPC74YV",
"callback_url": "https://sillega.co/api/vaki/callback"
}'

201 Created:

{
"id": "chl_01J9Z4M2K7QF3B",
"object": "checkout_link",
"url": "https://vaki.co/checkout/clinicalaliga?value=50000&currency=COP&ref=chl_01J9Z4M2K7QF3B",
"status": "open",
"amount": 50000,
"currency": "COP",
"fee_percent": 5,
"external_reference": "DON_59LPC74YV",
"expires_at": "2026-08-18T14:00:00Z",
"created_at": "2026-08-17T14:00:00Z"
}

Only vaki, amount and currency are required. Everything else is convenience: email prefills the donor’s address, external_reference is your own id coming back to you, callback_url is stored for when webhook delivery ships, and success_url / cancel_url control where the donor lands afterwards.

Terminal window
open "https://vaki.co/checkout/clinicalaliga?value=50000&currency=COP&ref=chl_01J9Z4M2K7QF3B"

That is the whole integration. Redirect, do not embed, and do not build this URL yourself — treat url as opaque so we can move the checkout later without breaking you.

Poll the link. status moves from open to completed once the payment is approved.

Terminal window
curl https://api.vaki.co/v1/checkout_links/chl_01J9Z4M2K7QF3B \
-H "Authorization: Bearer $VAKI_API_KEY"
{
"id": "chl_01J9Z4M2K7QF3B",
"object": "checkout_link",
"status": "completed",
"amount": 50000,
"currency": "COP",
"fee_percent": 5,
"external_reference": "DON_59LPC74YV",
"expires_at": "2026-08-18T14:00:00Z",
"created_at": "2026-08-17T14:00:00Z"
}

status is one of:

StatusMeaning
openMinted, not paid yet. Still usable.
completedThe donation was approved. Terminal.
expiredPassed expires_at without being paid. Mint a new one.
cancelledVoided before payment. Terminal.

A sensible polling shape: poll when the donor returns to your success_url, and then once a minute for a few minutes, backing off. Do not poll a link that has reached a terminal status, and do not poll every link you have ever minted on a cron — that is what the rate limit is there to stop.

const res = await fetch('https://api.vaki.co/v1/checkout_links', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VAKI_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `donation-${orderId}`
},
body: JSON.stringify({
vaki: 'clinicalaliga',
amount: 50000,
currency: 'COP',
email: 'donante@example.com',
external_reference: orderId
})
});
if (!res.ok) {
// Problem documents are application/problem+json — branch on `code`.
const problem = await res.json();
throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`);
}
const link = await res.json();
// Persist link.id against your own order before redirecting.
return link.url;
import os
import requests
res = requests.post(
"https://api.vaki.co/v1/checkout_links",
headers={
"Authorization": f"Bearer {os.environ['VAKI_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": f"donation-{order_id}",
},
json={
"vaki": "clinicalaliga",
"amount": 50000,
"currency": "COP",
"email": "donante@example.com",
"external_reference": order_id,
},
timeout=15,
)
if res.status_code != 201:
problem = res.json()
raise RuntimeError(f"{problem['code']}: {problem.get('detail', problem['title'])}")
link = res.json()
checkout_url = link["url"]

If the cause does not exist yet, create it. The owner must already be a registered Vaki account — pass their email and we resolve it.

Terminal window
curl https://api.vaki.co/v1/vakis \
-H "Authorization: Bearer $VAKI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: vaki-N_2QFBQFJTY" \
-d '{
"title": "Ayuda para el Hospital San Jorge",
"description": "Dotación de la sala de urgencias del Hospital San Jorge.",
"goal": { "amount": 50000000, "currency": "COP" },
"category": "no-profit",
"country": "CO",
"owner": { "name": "Bruno Ocampo", "email": "bruno@sillega.co" },
"external_reference": "N_2QFBQFJTY",
"state": "draft"
}'

201 Created returns the vaki, including the key you pass to POST /v1/checkout_links:

{
"key": "ayuda-hospital-san-jorge",
"slug": "ayuda-hospital-san-jorge",
"url": "https://vaki.co/ayuda-hospital-san-jorge",
"title": "Ayuda para el Hospital San Jorge",
"state": "draft",
"goal": { "amount": 50000000, "currency": "COP" },
"finance_data": { "comision": 5 },
"owners": [{ "name": "Bruno Ocampo", "email": "bruno@sillega.co" }],
"created_at": "2026-08-17T13:58:11Z"
}

A cause created in draft is not publicly visible. Publish it from the Vaki dashboard, or pass "state": "published" if you want it live immediately.

If the owner email does not resolve to an existing Vaki account you get a 422 with code: "owner_not_found". Creating an account on the owner’s behalf is coming soon.