Capital
Slate Capital is a fixed-amount financing product: your customer receives an upfront disbursement and repays it in equal scheduled installments. It is offered proactively, Slate underwrites your customer from the financial data you submit, issues a pre-approval (a non-binding offer), and the customer converts it into a financing agreement by completing an application and signing the contract. Slate handles underwriting, decisioning, the contract, the disbursement, and collection; your platform supplies the customer data and presents the offer.
Depending on who the customer is, the agreement is executed under one of two legal instruments:
| Type | Instrument | Used for |
|---|---|---|
fixed_mca |
Merchant Cash Advance (MCA) | Business customers |
fixed_fepa |
Future Earnings Purchase Agreement (FEPA) | Person customers |
Both behave identically in the API: the total to repay is fundedAmount × factorRate, split into fixed installments over the agreement's term. The only difference is the underlying contract and which object carries the factorRate (fixedMCA vs fixedFEPA).
This guide walks the full flow end to end: which endpoints to call, which webhooks to listen to, which components you can embed, and what your platform is expected to do at each step.
All amounts in the Capital API are integer cents (e.g.
2500000= $25,000.00). All dates areYYYY-MM-DDunless noted. All API requests are authenticated with your private API key in thex-api-keyheader. Webhooks are delivered and signed, see Webhooks for signature verification.
Capital does not use finance requests (those belong to the Early Wage Access product, see Early Wage Access) and has no billing periods or statements (those belong to Credit Lines).
Lifecycle at a glance
You create the customer ──► You attach financial data ──► Slate underwrites
│
▼
Pre-approval issued ──► webhook: preapproval.created
│
you present the offer (embed / own UI) │
▼
Customer opens the application
webhook: application.created
│
▼
Customer completes and submits
webhook: application.submitted
│
▼
Slate underwrites & approves with terms
webhook: application.ready-to-accept
│
▼
Customer verifies identity & accepts the terms
webhooks: application.approved · preapproval.consumed
│
▼
Agreement is created automatically when
terms are accepted
financing-agreement.created
│
▼
Disbursement ──► webhook: financing-agreement.disbursed
│
▼
Scheduled repayments ──► webhook: financing-agreement.repayment (each one)
│
┌────────────────┴───────────────┐
▼ ▼
COMPLETED DEFAULTED
webhook: financing-agreement.completed webhook: financing-agreement.defaulted
Key facts:
- The offer comes from Slate. You don't request financing for a specific amount; you submit financial data, and Slate issues a pre-approval when the customer qualifies.
- The application, contract and signature are hosted by Slate. Your platform embeds them; it never collects or forwards the application data itself.
- Repayment is automatic. Slate debits the installments from the customer's verified bank account (PAD). There is no endpoint to record payments, you only monitor them.
- A customer can be a business or a person. The customer type determines the instrument (
fixed_mcavsfixed_fepa); everything else in the flow is the same.
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"
}
}'
For a person:
curl -X POST "$BASE_URL/customers" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "PERSON",
"externalId": "cus_456",
"person": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@email.com",
"phone": "+15551234567"
}
}'
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.
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 with an amount, currency and repayment cadence. You receive:
Webhook — preapproval.created
{
"event_type": "preapproval.created",
"data": { "id": "019f8186-7021-73ea-a169-b12cd581c38c" },
"timestamp": "2026-06-20T16: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"
{
"data": [
{
"id": "019f8186-7021-73ea-a169-b12cd581c38c",
"customerId": "0195b7d2-...",
"externalId": "cus_123",
"status": "ACTIVE",
"amount": 2500000,
"currency": "CAD",
"repaymentFrequency": 1,
"repaymentFrequencyUnit": "month",
"origin": "slate",
"createdAt": "2026-06-20T16:32:00.000Z"
}
],
"pagination": { "page": 1, "limit": 10, "total": 1 }
}
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, signature required, 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
Inside the application flow the customer confirms their business or personal details and address, and connects their bank account through Flinks (this is the account Slate disburses to and debits from). Connecting the bank submits the application. Screens already covered by the data you sent when creating the customer are skipped, see The Application Flow for every screen and state in detail. Your platform does nothing here except listen:
| Webhook | When it fires |
|---|---|
application.created |
The customer opened the application |
application.submitted |
The customer completed and submitted it |
application.bank.verified |
The bank account was verified |
application.rejected |
Slate declined the application — the flow ends here |
data.id in every application.* webhook is the application id. Fetch the full picture any time:
curl "$BASE_URL/v2/applications/{applicationId}" \
-H "x-api-key: $API_KEY"
{
"application": {
"id": "01a0c2ff-...",
"customerId": "0195b7d2-...",
"externalId": "cus_123",
"currentStep": "BANK_INFORMATION",
"status": "PROCESSING",
"createdAt": "2026-06-21T10:12:00.000Z",
"updatedAt": "2026-06-21T10:19:42.000Z"
},
"preApproval": { "id": "019f8186-...", "status": "ACTIVE", "amount": 2500000, "...": "..." },
"financeRequest": null,
"customer": { "id": "0195b7d2-...", "externalId": "cus_123", "type": "BUSINESS", "...": "..." }
}
Application status moves through DRAFT → PROCESSING → APPROVED | REJECTED (REQUIRES_ACTION when the customer must resolve a pending task, CANCELLED if abandoned).
Step 6 — Terms review and acceptance
Slate underwrites the submitted application and makes a decision. If it's approved with terms, the application moves to REQUIRES_ACTION and you receive:
Webhook — application.ready-to-accept
{
"event_type": "application.ready-to-accept",
"data": { "id": "01a0c2ff-..." },
"timestamp": "2026-06-21T14:00:00Z"
}
The customer has two actions left, both inside Slate's flow: complete identity verification (IDV), then review and accept the approved terms; before accepting, they can reduce the amount, but never request more than what was approved. There is nothing for your platform to call; you can surface an "action needed" state in your UI if you build your own banner (Option B).
Step 7 — The financing agreement is created
When the customer accepts the terms, three things happen in sequence and you receive a webhook for each:
application.approved— the application reached its final state.preapproval.consumed— the offer is spent; the banner stops rendering it.financing-agreement.created— the executed agreement now exists.data.idis the financing agreement id — store it.
Fetch it:
curl "$BASE_URL/v2/financing-agreements/{id}" \
-H "x-api-key: $API_KEY"
{
"id": "01a0d411-...",
"customerId": "0195b7d2-...",
"type": "fixed_mca",
"fixedMCA": { "factorRate": 1.2 },
"status": "IN_PROGRESS",
"fundedAmount": 2500000,
"currency": "CAD",
"fundedDate": "2026-06-22",
"firstPaymentDate": "2026-07-22",
"estimatedLastPaymentDate": "2026-12-22",
"term": 6,
"termUnit": "month",
"repaymentFrequency": 1,
"repaymentFrequencyUnit": "month",
"disbursed": false,
"remainingBalance": 3000000,
"totalPaid": 0,
"payments": [],
"upcomingPayments": [
{ "amount": 500000, "currency": "CAD", "date": "2026-07-22T00:00:00Z" },
{ "amount": 500000, "currency": "CAD", "date": "2026-08-22T00:00:00Z" }
],
"createdAt": "2026-06-22T14:03:11.000Z",
"updatedAt": "2026-06-22T14:03:11.000Z"
}
Reading the terms: the customer received fundedAmount ($25,000) and repays fundedAmount × factorRate ($30,000) in equal installments every repaymentFrequency repaymentFrequencyUnit over term termUnit, 6 monthly payments of $5,000.
For a person customer the object is identical except "type": "fixed_fepa" and the factor rate lives in "fixedFEPA": { "factorRate": ... }.
Step 8 — Disbursement
Slate transfers the funds to the customer's verified bank account:
Webhook — financing-agreement.disbursed
{
"event_type": "financing-agreement.disbursed",
"data": { "id": "01a0d411-..." },
"timestamp": "2026-06-23T09:00:00Z"
}
The agreement's disbursed field flips to true. The repayment schedule runs from firstPaymentDate.
Step 9 — Repayment and monitoring
Slate collects each installment automatically via PAD from the customer's bank account. Every collected installment fires:
Webhook — financing-agreement.repayment
{
"event_type": "financing-agreement.repayment",
"data": { "id": "01a0d411-..." },
"timestamp": "2026-07-22T12:00:00Z"
}
After each one, refetch the agreement remainingBalance, totalPaid, payments and upcomingPayments are always current:
{
"payments": [
{ "amount": 500000, "currency": "CAD", "date": "2026-07-22T12:00:00Z", "type": "repayment", "status": "succeed" }
],
"upcomingPayments": [
{ "amount": 500000, "currency": "CAD", "date": "2026-08-22T00:00:00Z" }
],
"remainingBalance": 2500000,
"totalPaid": 500000
}
Payment status is in_progress, succeed or failed.
Step 10 — Present the agreement to your customer
Once the agreement exists, your customer needs a place to see it. Two ways to build it:
Option A — Embed slate-capital
The capital experience as a single embed for your customer's dashboard. It renders the customer's financing agreements: details, repayment progress, and the payment list; and lets the customer make an early payment: they pick an amount and authorize a PAD debit from their bank account for it, reducing the remaining balance ahead of schedule.
<slate-capital env="live" user-token="<session-token>"></slate-capital>
It also includes the pre-approval banner: whenever the customer has an active offer (their first one, or a new one after completing an agreement), it appears in the same view. Its main purpose, though, is giving the customer the full capital experience around their agreements. See the Capital Page component page for all its properties and caveats.
Because
slate-capitalalready embeds the pre-approval banner, never place it on the same page asslate-pre-approval-banner-v2— the banner would render twice.
Option B — Build your own screen
Everything the customer needs to see is in GET /v2/financing-agreements/{id} — terms, progress, payment history and the upcoming schedule. Refetch it after every financing-agreement.repayment webhook to keep it current.
Step 11 — Closure
The agreement ends in one of two states:
| Webhook | Meaning |
|---|---|
financing-agreement.completed |
The full amount was repaid; status becomes COMPLETED |
financing-agreement.defaulted |
Slate marked the agreement as defaulted; status becomes DEFAULTED |
A customer with a completed agreement keeps generating financial data (Step 2) keep attaching it, and Slate issues new pre-approvals as they qualify again. The cycle restarts at Step 3.
Monitoring endpoints
| Endpoint | What it returns |
|---|---|
GET /customers |
Paginated customers. Filters: externalId, type, product=CAPITAL |
GET /v2/pre-approvals |
Paginated pre-approvals. Filters: customerId, externalId, status |
GET /v2/applications |
Paginated applications. Filters: customerId, externalId, status |
GET /v2/applications/{id} |
One application with its pre-approval and customer |
GET /v2/financing-agreements |
Paginated agreements. Filters: externalId, status, type, excludeEwa=true to keep only Capital agreements |
GET /v2/financing-agreements/{id} |
One agreement with payments, upcomingPayments, remainingBalance, totalPaid |
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 an 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 agreement | Remove it from your UI if you built your own banner |
application.created |
Application | The customer opened the application | Track engagement |
application.submitted |
Application | The application was submitted | Show an "under review" state |
application.bank.verified |
Application | The bank account was verified | Nothing — informational |
application.ready-to-accept |
Application | Approved with terms; the customer must verify identity and accept | Show an "action needed" state |
application.approved |
Application | The customer accepted the terms; final approval | Show an "approved" state |
application.rejected |
Application | Slate declined the application | Show a "declined" state |
financing-agreement.created |
Financing agreement | The executed agreement exists | Store the agreement id |
financing-agreement.disbursed |
Financing agreement | Funds were sent to the customer | Reflect it to the customer |
financing-agreement.repayment |
Financing agreement | An installment was collected | Refetch the agreement |
financing-agreement.completed |
Financing agreement | Fully repaid | Reflect it; new offers may follow |
financing-agreement.defaulted |
Financing agreement | The agreement defaulted | Reflect it in your UI |
Good practices
- Keep financial data fresh. Offers are only as good as the data behind them, attach new timeseries on a regular cadence, not just once.
- Use
externalIdeverywhere. It's the same identifier across customers, pre-approvals, applications and agreements, and every list endpoint filters by it. - Treat webhooks as notifications, not payloads. They carry only the object id, always fetch the current state from the API afterward.
- Mint session tokens server-side, per customer, on demand. Never ship your API key to the browser, and never reuse one customer's token for another.
- Don't compute balances client-side.
remainingBalance,totalPaidand the payment schedule come from Slate's ledger, read them from the agreement endpoint. - Filter with
excludeEwa=truewhen listing agreements for a Capital dashboard, so EWA advances (if you also use that product) don't mix in.