AlkiraiLinkDocs

Guides

Syncing and budgets

Reading a connection signs in to the institution, and institutions notice an account that signs in too often. So every connection has a sign-in budget, and the API is shaped to make one sign-in go a long way.

One refresh, one sign-in

POST /v1/connections/{connection_id}/refresh reads accounts and a transaction sync from one sign-in. Use it whenever you want both.

GET /v1/connections/{connection_id}/accounts followed by POST /v1/connections/{connection_id}/sync reads the same things with two sign-ins, seconds apart. That pattern looks like a script to an institution, and at an institution with a small budget the second call is refused.

Cursors

A sync returns what changed since the cursor you send, and a new cursor.

  1. Send no cursor the first time.
  2. Write added and modified by transaction_id, and delete removed_ids.
  3. Store the returned cursor with the connection, and send it next time exactly as it came.

A cursor is opaque: don't read anything into it. A sync can return a transaction you already have, so always upsert by transaction_id rather than insert. History can also arrive over more than one sync, so a first sync may not reach as far back as the next one does.

curl -X POST "https://api.alkirai.com/v1/connections/$CONNECTION_ID/sync?cursor=$CURSOR" \
  -H "X-Client-Id: $ALKIRAILINK_CLIENT_ID" \
  -H "X-API-Key: $ALKIRAILINK_SECRET"

When partial_failure is set, the read stopped partway. What arrived is real: store it and the cursor, record the refresh as incomplete, and sync again later.

Reading straight after a connect

The connect has just signed in, with the person there. AlkiraiLink keeps that session for 5 minutes waiting for your first refresh or sync, and answers it from there. For 15 minutes after the connect, reads of the connection are answered from that first read without signing in again, whatever cursor you send.

So read a new connection soon after the exchange, and expect the reads in that window to repeat what you already have.

Sign-in budgets

Each institution allows a number of sign-ins per connection in a window of hours, and the connect's own sign-in counts. Read where a connection stands with GET /v1/connections/{connection_id}, which signs in to nothing:

"sign_ins": {
  "recent": 1,
  "limit": 3,
  "window_hours": 6.0,
  "last_at": "2026-10-20T07:00:12Z",
  "next_at": null
}

Never retry a read in a loop. Each try can spend a sign-in. Retry only when error.retryable is true, and wait for error.retry_after when it is set.

Connection states

GET /v1/connections/{connection_id} and GET /v1/connections report what the next sync would meet.

StateWhat it meansWhat to do
activeA sync would sign in now.Refresh on your schedule.
at_limitThe sign-in budget is spent until sign_ins.next_at.Wait. Nothing is wrong.
stoppedSyncs are refused, without contacting the institution, until something changes. error says what stopped it.Follow error.user_action: see below.

When a connection stops

A sign-in that fails, for any reason that isn't retryable, stops the connection rather than trying the institution again. Every read then answers 409 with the same error until the cause is cleared, and nothing more is sent to the institution.

Show the person error.display_message when it is set. It is written for them; error.message is for you.

Categories and merchant names

Every transaction a sync or a refresh returns carries a personal_finance_category (PersonalFinanceCategory) and, where a business can be named, a merchant_name. Both are read from the institution's own description with rules written for the region.

Treat a category as a default. When the person changes one, or your own rules say otherwise, keep their choice: a later sync sends the same row with the same category again.

A refresh schedule