Credit Lines
A Slate Credit Line is a revolving spending facility for your customers. Your platform records the customer's activity (purchases, payments, rebates) in real time, and Slate handles the billing: it groups activity into billing periods, generates an immutable statement at the end of each period, and manages the line's lifecycle (active → suspended → reactivated) based on whether the minimum payment is met.
This guide walks through the full flow end to end: which endpoints to call, which webhooks to listen to, and what your platform is expected to do at each step.
All amounts in the Credit Line API are integer cents (e.g.
50000= $500.00). All dates areYYYY-MM-DD. All requests are authenticated with your private API key in thex-api-keyheader. Webhooks are delivered and signed, see Webhooks for signature verification.
Lifecycle at a glance
You create the customer ──► You attach financial data ──► Slate underwrites
│
▼
Pre-approval issued ──► webhook: preapproval.created
│
you present the offer │
and the customer applies │
▼
Application approved ──► You activate the line ──► ACTIVE
│
┌────────────────────────────┘
▼
Billing period (7 days)
customer spends / pays / receives rebates
│ period ends
▼
Statement generated ──► webhook: credit-line.statement-ready
│
▼
Grace period (3 days) — customer pays the minimum
│
┌─────────────┴──────────────┐
▼ ▼
Minimum covered Minimum NOT covered
line stays ACTIVE line is SUSPENDED
next period continues webhook: credit-line.suspended → block the card
│
▼
Customer pays the missed minimum
line auto-reactivates
webhook: credit-line.reinstated → re-enable the card
Key facts:
- Billing periods are 7 days, anchored to the
anchorDateyou set when activating the line, followed by a 3-day grace period to pay the statement's minimum. - Statements are immutable snapshots: once generated, a statement never changes. Payments received afterward appear on the next period's statement.
- If a balance is carried past its due date, Slate bills a carried balance fee into the next period. It appears as a
feetransaction on the line. - Suspension and reactivation are automatic. You don't call any endpoint to reactivate, a sufficient payment does it.
Step 1 — Create the customer
Create the customer with your own stable identifier (externalId) — it is your reconciliation key across every Slate object. A customer is either a BUSINESS or a PERSON.
curl -X POST "$BASE_URL/customers" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "BUSINESS",
"externalId": "cus_123",
"business": {
"legalName": "Acme Corp",
"dba": "Acme",
"website": "https://acme.com"
}
}'
All detail fields are optional at creation — whatever you don't provide, the customer fills in during the application. The more you provide upfront, the shorter their application. If the customer already exists (for example, they already use another Slate product), skip this step.
Webhook — customer.created confirms the customer exists; data.id is the customer id.
Step 2 — Attach financial data
Submit the customer's financial history so Slate can underwrite. The payload is a timeseries keyed by your externalId:
curl -X POST "$BASE_URL/attach-financial-data" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "cus_123",
"timeseries": [
{ "date": "2026-04-01", "data": { "revenue": 4200000, "orders": 310 } },
{ "date": "2026-05-01", "data": { "revenue": 4550000, "orders": 335 } },
{ "date": "2026-06-01", "data": { "revenue": 4830000, "orders": 342 } }
]
}'
The data object is free-form — send the metrics agreed with Slate for your integration (revenue is the baseline). You can attach new data at any time to keep the picture current; fresher data means better offers.
Webhook — business.financial-data.attached confirms ingestion; data.id is the financial data record id.
Step 3 — Slate issues a pre-approval
When the customer qualifies, Slate creates a pre-approval — a non-binding offer whose amount is the facility limit Slate is prepared to extend on a credit line. You receive:
Webhook — preapproval.created
{
"event_type": "preapproval.created",
"data": { "id": "019f8186-7021-73ea-a169-b12cd581c38c" },
"timestamp": "2026-06-18T16:32:00Z"
}
Fetch the customer's offers any time:
curl "$BASE_URL/v2/pre-approvals?externalId=cus_123&status=ACTIVE" \
-H "x-api-key: $API_KEY"
A pre-approval is ACTIVE until it either expires (webhook preapproval.expired) or the customer converts it by completing an application (webhook preapproval.consumed).
Step 4 — Present the offer
Everything the customer sees is rendered by Slate components, authenticated with a short-lived session token that you mint server-side for the logged-in customer:
curl -X POST "$BASE_URL/user-session-token" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalId": "cus_123" }'
{ "token": "eyJhbGciOi..." }
Never expose your API key to the browser, only the session token. Load the components bundle once per page:
<script async type="module" src="https://components.tryslatehq.com/slate.esm.js"></script>
You have two options for implementing the application flow, from least to most integration work:
Option A — slate-pre-approval-banner (easiest)
The banner manages the entire lifecycle by itself: it renders only when the customer has an active offer, opens Slate's application flow when clicked, and updates its own state as the application progresses (in progress, submitted, approved, rejected). Drop it in and you're done.
<slate-pre-approval-banner-v2
env="live"
user-token="<session-token>"
></slate-pre-approval-banner-v2>
See the Pre Approval Banner component page for all its properties and caveats.
Option B — Your own banner + slate-application-form
Build the banner/offer UI yourself and open Slate's application form when the customer engages. You drive your banner's state by listening to the webhooks (preapproval.*, application.*) or by polling GET /v2/pre-approvals and GET /v2/applications, and embed only the application itself:
<slate-application-form
id="application-form"
env="live"
user-token="<session-token>"
closable
></slate-application-form>
<script>
const form = document.getElementById("application-form");
form.addEventListener("submit", ({ detail }) => {
// detail.applicationId — the application was submitted
});
form.addEventListener("dismiss", ({ detail }) => {
// detail.status is "submitted" | "approved" | "rejected" — close the form
});
</script>
The form renders as an iframe that fills its parent, so give the container an explicit height. Omit finance-request-id for Capital, the form automatically loads the application tied to the customer's pre-approval. See the Application Form component page for all its properties, events and caveats.
Step 5 — The customer applies and is approved
The customer completes the application inside Slate's flow — confirming their details and connecting their bank account. Screens already covered by the data you sent when creating the customer are skipped. Slate reviews the application, and when it's approved you receive:
Webhook — credit-line.approved
{
"event_type": "credit-line.approved",
"data": { "id": "9c5f8f4e-...-application-uuid" },
"timestamp": "2026-06-20T16:32:00Z"
}
data.id is the application id. The application now carries an approved maximum facility limit. Fetch it if you need the details:
curl "$BASE_URL/v2/applications/{applicationId}" \
-H "x-api-key: $API_KEY"
Your platform can now activate the credit line.
Step 6 — Activate the credit line
Create the line from the approved application. You choose the facility limit (up to the approved maximum), the currency, and the anchor date — the day the first billing period starts. All subsequent periods are derived from this date, so pick it deliberately (usually "today" or the customer's go-live date).
curl -X POST "$BASE_URL/applications/{applicationId}/credit-line" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"facilityLimit": 1500000,
"currency": "CAD",
"anchorDate": "2026-06-22"
}'
Response
{
"id": "019f8186-7021-73ea-a169-b12cd581c38c",
"applicationId": "9c5f8f4e-...",
"customerId": "0195b7d2-...",
"facilityLimit": 1500000,
"currency": "CAD",
"status": "active",
"anchorDate": "2026-06-22",
"createdAt": "2026-06-22T14:03:11.000Z",
"updatedAt": "2026-06-22T14:03:11.000Z"
}
The line is active and its first billing period is open. You also receive:
Webhook — credit-line.created
{
"event_type": "credit-line.created",
"data": { "id": "019f8186-7021-73ea-a169-b12cd581c38c" },
"timestamp": "2026-06-22T14:03:11Z"
}
From here on, data.id in every credit line webhook is the credit line id.
Step 7 — Record transactions in real time
Every time something happens on the card, push it to Slate as it happens through a single endpoint:
POST /credit-lines/{creditLineId}/transactions
| Field | Type | Description |
|---|---|---|
type |
"purchase" | "payment" | "rebate" |
What kind of transaction this is |
amount |
integer | Amount in cents, always positive |
transactionDate |
string | YYYY-MM-DD — the date the transaction happened |
externalId |
string (optional) | Your transaction id, for reconciliation |
description |
string (optional) | Free-text description (e.g. merchant name) |
Transaction types
| Type | What it is | Rules |
|---|---|---|
purchase |
Customer card spend | Rejected while the line is suspended or the period the date falls in is already closed |
payment |
Money the customer paid toward the line | Accepted at any time — including while suspended. Payments count toward the minimum payment and can automatically reactivate a suspended line |
rebate |
A credit funded by you (the partner), e.g. a fuel rebate | Reduces the outstanding balance, but does not count toward the customer's minimum payment and does not pay down fees |
Examples
A purchase:
curl -X POST "$BASE_URL/credit-lines/{creditLineId}/transactions" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "purchase",
"amount": 15000,
"transactionDate": "2026-06-25",
"externalId": "txn_8f2c31",
"description": "PETRO-CANADA 38989"
}'
A customer payment:
curl -X POST "$BASE_URL/credit-lines/{creditLineId}/transactions" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "payment",
"amount": 250000,
"transactionDate": "2026-06-28",
"externalId": "etransfer_a91b",
"description": "E-Transfer Payment"
}'
A partner-funded rebate:
curl -X POST "$BASE_URL/credit-lines/{creditLineId}/transactions" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "rebate",
"amount": 560,
"transactionDate": "2026-07-01",
"description": "Fuel Rebate"
}'
Response (all types)
{
"id": "01a0c2ff-...",
"creditLineId": "019f8186-...",
"periodId": "01a0c2f0-...",
"type": "purchase",
"amount": 15000,
"transactionDate": "2026-06-25",
"description": "PETRO-CANADA 38989",
"notes": "txn_8f2c31",
"createdAt": "2026-06-25T18:40:12.000Z"
}
Slate assigns each transaction to the billing period its transactionDate belongs to. You may also see fee transactions on the line — those are created by Slate (e.g. the carried balance fee) and cannot be created through the API.
Common errors:
INVALID_STATE— you tried to record a purchase on a suspended line, or a transaction dated in a period that is already closed. Payments dated within a period's grace window are accepted until the grace period ends.
Step 8 — The statement is generated
When a billing period ends, Slate closes it and generates its statement. You receive:
Webhook — credit-line.statement-ready
{
"event_type": "credit-line.statement-ready",
"data": { "id": "019f8186-7021-73ea-a169-b12cd581c38c" },
"timestamp": "2026-06-29T00:05:00Z"
}
data.id is the credit line id. The webhook is a notification only — fetch the statement to get its contents:
curl "$BASE_URL/credit-lines/{creditLineId}/statement" \
-H "x-api-key: $API_KEY"
Without parameters this returns the most recent statement — which right after the webhook is the one that was just generated.
Response
{
"creditLineId": "019f8186-...",
"currency": "CAD",
"period": {
"id": "01a0c2f0-...",
"periodNumber": 0,
"periodStart": "2026-06-22",
"periodEnd": "2026-06-28",
"periodDays": 7,
"paymentDueDate": "2026-07-01",
"graceExpiry": "2026-07-01",
"status": "grace_period"
},
"summary": {
"facilityLimit": 1500000,
"openingBalance": 0,
"totalPurchases": 61456,
"totalPayments": 0,
"totalRebates": 0,
"totalFees": 0,
"statementBalance": 61456,
"minimumPayment": 3125,
"amountPaid": 0,
"carriedBalance": 61456,
"availableCredit": 938544
},
"transactions": [
{
"id": "01a0c2ff-...",
"type": "purchase",
"amount": 25000,
"transactionDate": "2026-06-25",
"description": "PETRO-CANADA 38989"
}
],
"generatedAt": "2026-06-29T00:05:01.000Z"
}
Show the statement to your customer. The two numbers that matter most:
summary.statementBalance— what the customer owes as of the period close. A negative value is a credit in the customer's favor.summary.minimumPayment— what the customer must pay byperiod.paymentDueDateto keep the line active.
Step 9 — The grace period
After the statement, the customer has until paymentDueDate (3 days after the period ends) to pay at least the minimum. Payments during this window are recorded exactly like any other payment (Step 7) — just make sure transactionDate reflects the real payment date, since that is what determines whether the payment arrived in time.
Two outcomes:
- Minimum covered by the due date → nothing happens; the line continues normally into the next period. The statement stays frozen; the payment appears on the next period's statement.
- Minimum not covered → the unpaid balance carries into the next period, a carried balance fee is billed (you'll see it as a
feetransaction), and the line is suspended.
Only
paymenttransactions count toward the minimum. Rebates reduce what the customer owes overall, but the minimum itself must be paid by the customer.
Step 10 — Suspension
If the grace period expires without the minimum being covered, you receive:
Webhook — credit-line.suspended
{
"event_type": "credit-line.suspended",
"data": { "id": "019f8186-7021-73ea-a169-b12cd581c38c" },
"timestamp": "2026-07-02T00:05:00Z"
}
What your platform must do: block the card. While suspended:
purchasetransactions are rejected withINVALID_STATE.paymenttransactions are still accepted — that's how the customer gets back to active.
You can check the line's status any time with GET /credit-lines/{creditLineId} (status: "suspended").
Step 11 — Reactivation
Reactivation is automatic. Keep recording the customer's payments as usual; once their payments cover the missed minimum payment (partial payments accumulate) — or pay off the outstanding balance entirely — the line reactivates on its own and you receive:
Webhook — credit-line.reinstated
{
"event_type": "credit-line.reinstated",
"data": { "id": "019f8186-7021-73ea-a169-b12cd581c38c" },
"timestamp": "2026-07-03T15:20:00Z"
}
What your platform must do: re-enable the card. The line is active again and purchases are accepted.
Fetching historical statements
Every closed period has its own immutable statement. There are two ways to get a specific one:
Option A — by date (easiest)
Pass any date inside the period you want to GET /statement:
curl "$BASE_URL/credit-lines/{creditLineId}/statement?statementDate=2026-06-25" \
-H "x-api-key: $API_KEY"
This returns the statement of the period that contains 2026-06-25.
Option B — by period id
List the line's periods, pick the one you want, and request its statement by id:
curl "$BASE_URL/credit-lines/{creditLineId}/periods" \
-H "x-api-key: $API_KEY"
[
{
"id": "01a0c2f0-...",
"periodNumber": 0,
"periodStart": "2026-06-22",
"periodEnd": "2026-06-28",
"graceExpiry": "2026-07-01",
"status": "finalized",
"statementBalance": 61456,
"minimumPayment": 3125,
"amountPaid": 0,
"carriedBalance": 61456,
"feeCharged": 410,
"createdAt": "2026-06-22T14:03:11.000Z"
},
{
"id": "01a0c9d1-...",
"periodNumber": 1,
"periodStart": "2026-06-29",
"periodEnd": "2026-07-05",
"graceExpiry": "2026-07-08",
"status": "open",
"statementBalance": null,
"minimumPayment": null,
"amountPaid": null,
"carriedBalance": null,
"feeCharged": null,
"createdAt": "2026-06-29T00:05:01.000Z"
}
]
Then:
curl "$BASE_URL/credit-lines/{creditLineId}/periods/{periodId}/statement" \
-H "x-api-key: $API_KEY"
periodsalso supportsdateFrom/dateTo(YYYY-MM-DD) query parameters to narrow the range. Statements only exist for periods whosestatusis notopen— an open period has no statement yet.
Monitoring endpoints
| Endpoint | What it returns |
|---|---|
GET /credit-lines |
Paginated list of your credit lines with their current period dates. Filters: status, search, page, limit |
GET /credit-lines/{creditLineId} |
A single line: status, terms, current period, live outstanding balance and availableCredit |
GET /credit-lines/{creditLineId}/transactions |
Paginated transactions. Filters: periodId, type, status, description, dateFrom, dateTo |
GET /credit-lines/{creditLineId}/periods |
All billing periods with their statement figures |
GET /credit-lines/{creditLineId}/statement |
The most recent statement, or a specific one via statementDate |
GET /credit-lines/{creditLineId}/periods/{periodId}/statement |
The statement of an exact period |
Example — the live view of a line:
curl "$BASE_URL/credit-lines/{creditLineId}" \
-H "x-api-key: $API_KEY"
{
"id": "019f8186-...",
"status": "active",
"facilityLimit": 1500000,
"currency": "CAD",
"anchorDate": "2026-06-22",
"currentPeriod": {
"periodStart": "2026-06-29",
"periodEnd": "2026-07-05",
"graceExpiry": "2026-07-08"
},
"balance": {
"currency": "CAD",
"balance": 61456,
"totalOutstanding": 61456
},
"availableCredit": 1438544,
"createdAt": "2026-06-22T14:03:11.000Z",
"updatedAt": "2026-06-29T00:05:01.000Z"
}
Webhooks summary
Subscribe to these events (see Webhooks for delivery and signature verification). Every payload has the same shape: { event_type, data: { id }, timestamp }.
| Event | data.id refers to |
When it fires | What you should do |
|---|---|---|---|
customer.created |
Customer | The customer was created | Store the customer id |
business.financial-data.attached |
Financial data record | Financial data was ingested | Nothing — confirmation only |
preapproval.created |
Pre-approval | Slate issued a facility limit offer | Present it (Step 4) |
preapproval.expired |
Pre-approval | The offer expired unused | Remove it from your UI if you built your own banner |
preapproval.consumed |
Pre-approval | The offer converted into an application | Remove it from your UI if you built your own banner |
credit-line.approved |
Application | The application is approved with a maximum facility limit | Activate the line (Step 6) |
credit-line.created |
Credit line | The line is created and its first period opens | Store the credit line id; start recording transactions |
credit-line.statement-ready |
Credit line | A billing period closed and its statement was generated | GET /statement and present it to your customer |
credit-line.suspended |
Credit line | The grace period expired without the minimum covered | Block the card; keep recording payments |
credit-line.reinstated |
Credit line | Payments covered the missed minimum (or the full balance) | Re-enable the card |
Good practices
- Push transactions as they happen. Statements, balances and suspension decisions are computed from what you've recorded — delayed transactions can land in a closed period and be rejected.
- Always send
externalIdon transactions. It's stored with the transaction and is your reconciliation key. - Use real transaction dates.
transactionDate(not the time you call the API) determines which period a transaction belongs to and whether a payment arrived within the grace window. - Treat webhooks as notifications, not payloads. They carry only the object id — always fetch the current state from the API afterward.
- Don't infer amounts client-side. Statement balances, minimum payments and fees are computed by Slate; read them from the statement endpoints.