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.
1. Put your key in the environment
Section titled “1. Put your key in the environment”Never commit it, never put it in a query string, never ship it to a browser.
export VAKI_API_KEY="…the key Vaki issued you…"2. Mint a checkout link
Section titled “2. Mint a checkout link”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¤cy=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.
3. Send the donor to url
Section titled “3. Send the donor to url”open "https://vaki.co/checkout/clinicalaliga?value=50000¤cy=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.
4. Confirm the donation
Section titled “4. Confirm the donation”Poll the link. status moves from open to completed once the payment is
approved.
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:
| Status | Meaning |
|---|---|
open | Minted, not paid yet. Still usable. |
completed | The donation was approved. Terminal. |
expired | Passed expires_at without being paid. Mint a new one. |
cancelled | Voided 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.
The same thing in Node
Section titled “The same thing in Node”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;The same thing in Python
Section titled “The same thing in Python”import osimport 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"]Optional: create the cause first
Section titled “Optional: create the cause first”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.
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.
- Authentication — key handling, rotation, IP allowlisting
- Accept a donation from your own site — the whole integration as a change to one route
- Errors — the codes you will hit first
- Fees — what 5% covers