AltScore
Credit Management System (CMS)

Credit Lines API

In company mode a company holds one credit card and opens credit lines under it — one per holder, vehicle or branch. Each line is a spending instrument with its own cap. The card still owns everything financial: the limit, the billing cycle, the cut-off, interest, late fees, payments and the statement. A purchase made with a line posts to the company's card and is attributed to that line.

The tenant has to be in "mode": "company-lines" (see Card Settings). The CMS Base URL is used (see introduction).

Lines are not clients

A line has no client or borrower of its own: it hangs from the card. Use label, referenceId and metadata to identify who or what it belongs to. If each holder must be a debtor with their own identity and debt, that is a client with their own card, not a line.

How to read this page

Every flow on this page, with its diagram and the exact order of the checks, is also in Card Flows.

The typical flow, in order:

  1. Open the lines and distribute the card's limit.
  2. Send each purchase with its line.
  3. Read how an overrun is reported.
  4. When interest or late fees eat the card's room, fit the lines to the card.
  5. Check how payments free the lines.
  6. Every write is safe to retry.

The Credit Line Object

A real line from a company card in staging, after a forced purchase broke its cap:

{
  "id": "6f1d6a1e-0000-4000-8000-000000000005",
  "accountId": "6f1d6a1e-0000-4000-8000-000000000002",
  "referenceId": "fleet-sales",
  "label": "Ventas",
  "status": "enabled",
  "creditLimit": { "amount": "2000.00", "currency": "USD" },
  "consumed": { "amount": "2600.00", "currency": "USD" },
  "availableCredit": { "amount": "-600.00", "currency": "USD" },
  "effectiveAvailable": { "amount": "0.00", "currency": "USD" },
  "currency": "USD"
}

Attributes

AttributeDescriptionType
idLine idUUID
accountIdThe company card it belongs toUUID
referenceIdOptional idempotency key from the partnerString
labelWho or what the line is for. Shown on the statement next to each purchaseString
statusSee Line StatusString
creditLimitThe line's capMoney
consumedThe line's purchases, minus reversals of them, minus what the company's payments gave back to it. Interest and late fees are never in a line: they are charged to the cardMoney
availableCreditcreditLimit − consumed. Negative when the line is over its cap — it is never clamped at zeroMoney
heldCupo retained by the line's active authorizations, already subtracted from availableCredit and effectiveAvailable. Not part of consumedMoney
effectiveAvailableWhat a purchase on this line could actually take today: availableCredit capped by what the card can still spend, never below zero, and 0 when the line or the card cannot buy. Use this one to show a holder their room. Read-onlyMoney

Line Status

StatusSpendsHolds its cupo
enabledYesYes
disabledNo (not even with force)Yes — suspended, not surrendered
closedNoNo — its cupo goes back to the company. Terminal

Endpoints

List the Lines

GET /v1/card-accounts/{id}/credit-lines

Every line of the card, closed ones included, with consumed, availableCredit and effectiveAvailable computed at read time. It returns all of them in one array — it does not paginate. The numbers come from account-level aggregates, not one query per line, so a card with hundreds of lines is still one call.

Get One Line

GET /v1/card-accounts/{id}/credit-lines/{lineId}

The same view as the list, for one line. 404 if the line is not on this card. Use it to check a single holder without reading all the lines.

Checking both caps before a purchase

There is no dry-run endpoint, and you do not need one: effectiveAvailable is the answer. It is the line's room capped by what the card can spend, so a purchase of X without force on that line passes both caps exactly when X ≤ effectiveAvailable. The two numbers it combines are also available on their own: the line's availableCredit, and the card's availableCredit on GET /v1/card-accounts/{id}/summary. It does not check the date rules (a future date, before the card opened, after expiresOn), which depend on the request, not on the balances.

Open a Credit Line

POST /v1/card-accounts/{id}/credit-lines
FieldDescriptionType
labelWho or what the line is for (required)String
creditLimitThe line's cap, in the card's currency (required)Money
referenceIdOptional idempotency key, unique per tenantString
metadataFree-form partner dataObject
{
  "referenceId": "fleet-north",
  "label": "Flota Norte",
  "creditLimit": { "amount": "3000.00", "currency": "USD" }
}

201 with the line. The sum of the limits of the live lines (enabled and disabled) cannot exceed the card's limit: a line that would break it answers 422 credit_line_over_assigned. No line can be opened while the card is over its limit.

Change a Line's Limit

PUT /v1/card-accounts/{id}/credit-lines/{lineId}/credit-limit

Body: { "creditLimit": { "amount": "2500.00", "currency": "USD" } }.

  • Raising must still fit the card (422 credit_line_over_assigned) and is refused while the card is over its limit (422 card_over_limit).
  • Lowering is always allowed, in any mode and with the card over its limit: it is the way out. A line can go below what it consumed; its availableCredit then turns negative.
  • Setting the same value is a no-op.

Enable, Disable or Close a Line

PUT /v1/card-accounts/{id}/credit-lines/{lineId}/status

Body: { "status": "enabled" | "disabled" | "closed" }. Closing returns the line's cupo to the company; disabling does not. Closing the card closes all its lines in the same operation.

Credit Relief

GET /v1/card-accounts/{id}/credit-relief

The card's situation and the way out of it. A real response from staging — a company card whose interest and late fees pushed it over its limit:

{
  "over":         { "amount": "1216.77", "currency": "USD" },
  "recoverable":  { "amount": "1000.00", "currency": "USD" },
  "blocked": true,
  "unbacked":     { "amount": "1000.00", "currency": "USD" },
  "outsideLines": { "amount": "1616.77", "currency": "USD" },
  "idle": [
    { "lineId": "6f1d6a1e-0000-4000-8000-000000000003", "label": "Flota Sur",
      "limit": { "amount": "3000.00", "currency": "USD" },
      "used":  { "amount": "2000.00", "currency": "USD" },
      "idle":  { "amount": "1000.00", "currency": "USD" } }
  ],
  "fit": [
    { "lineId": "6f1d6a1e-0000-4000-8000-000000000003", "label": "Flota Sur",
      "limit":          { "amount": "3000.00", "currency": "USD" },
      "used":           { "amount": "2000.00", "currency": "USD" },
      "cut":            { "amount": "1000.00", "currency": "USD" },
      "suggestedLimit": { "amount": "2000.00", "currency": "USD" },
      "closeSuggested": false }
  ]
}
AttributeMeaning
overHow much the card is past its limit. Zero when it is not
idleEach live line's unused cupo, largest first
recoverableSum of idle: what can be freed without touching anybody's spending
blockedA signal: the card is over its limit and its idle line cupo would not even cover the overdraft. Any overdraft already refuses every non-forced purchase — the card cap never looks at the lines, so freeing idle lines does not unblock purchases; only a payment, a reversal, a higher card limit or force does. Derived, never stored
outsideLinesThe card's debt that no line carries: interest, late fees, and any purchase booked without a line
unbackedCupo the lines still show as available that the card can no longer back. Positive means a warning
fitThe reduction per line that brings unbacked to zero. See below

Fit the Lines to the Card

POST /v1/card-accounts/{id}/credit-lines/fit

No body. Lowers the lines as fit suggests, in one operation, and answers the credit relief after the change. See When the card cannot back the lines.

Purchases against a line

POST /v1/card-accounts/{id}/purchases
{
  "referenceId": "pos-000123",
  "amount": { "amount": "200.00", "currency": "USD" },
  "creditLineId": "6f1d6a1e-0000-4000-8000-000000000005",
  "force": false
}
  • A purchase must fit both caps, the line's and the card's. The line is checked first, so the error names the line: 422 insufficient_line_credit ("this holder has no room") before 422 insufficient_credit ("the company has no room").
  • A card with live lines only spends through them: a purchase without creditLineId answers 422 credit_line_required. A card without lines keeps buying directly.
  • A creditLineId outside company mode answers 422 card_mode_mismatch; a line from another card, 404; a line that is not enabled, 412.
  • force: true books the purchase even when it breaks a cap — for purchases a processor already authorized and settled, where refusing would only keep real money out of the ledger. It skips the cap, never the status: a disabled or closed line does not spend, forced or not.
  • A reversal of the purchase gives the cupo back to the same line.

See Credit Cards for the rest of the purchase contract (dates, idempotency, reversals).

No credit: the line or the card?

A purchase with a line passes two checks, in this order: the line (what this holder has) and then the card (what the company has). Each refusal says which one failed, in the code, the message and error.details:

422
{ "error": {
    "code": "insufficient_line_credit",
    "message": "purchase of $500.00 exceeds the available credit $362.01 of line \"Ana\"",
    "details": { "scope": "line", "amount": { "amount": "500.00", "currency": "USD" },
                 "creditLineId": "6f1d6a1e-0000-4000-8000-000000000005", "creditLineLabel": "Ana",
                 "lineAvailable": { "amount": "362.01", "currency": "USD" } } } }
422
{ "error": {
    "code": "insufficient_credit",
    "message": "purchase of $100.00 exceeds the card's available credit -$569.59; the card is over its limit by $569.59 (its debt includes interest and charges); line \"Juan\" has $1,045.25 available, but the card cannot back it — register a payment or raise the card limit",
    "details": { "scope": "card", "amount": { "amount": "100.00", "currency": "USD" },
                 "cardAvailable": { "amount": "-569.59", "currency": "USD" },
                 "cardOverLimit": { "amount": "569.59", "currency": "USD" },
                 "creditLineId": "6f1d6a1e-0000-4000-8000-000000000003", "creditLineLabel": "Juan",
                 "lineAvailable": { "amount": "1045.25", "currency": "USD" } } } }
details.scopeMeaningWhat to do
lineThis holder ran out of cupo on their lineRaise the line, reduce another line to make room, or wait for a payment to free it
cardThe company card has no room — usually because interest and charges live on the card — even if the line still shows cupoRegister a payment or raise the card limit. Reducing lines does not help: the card cap never looks at the lines

cardOverLimit is present only when the card is over its limit; the line keys only when the purchase named a line. The codes are unchanged, so an integration that already handles them keeps working; details is additive.

Show effectiveAvailable, not availableCredit, to a holder

availableCredit is the line's own room. effectiveAvailable caps it by what the card can actually spend: in the example above Juan's line shows availableCredit 1,045.25 and effectiveAvailable 0, so the holder knows before trying that the purchase will be refused.

How an overrun is reported

There are two caps, and either can be broken — by a forced purchase, or (the card's) by interest and late fees. Each surface tells you which one:

WhereWhat you see
The purchase (metadata)overLimit: true on every forced purchase that broke a cap, plus lineOverLimitAmount (how far past the line) and/or overLimitAmount (how far past the card). One filter on overLimit finds every exception
The event streamcard.purchase.over-limit, emitted next to card.purchase.registered, with creditLineId + lineOverLimit when the line broke, and overLimit + creditLimit when the card did
The lineavailableCredit negative (-600.00 in the example above)
The cardavailableCredit negative on the account and the summary; credit-relief.over positive
Credit reliefblocked, unbacked and outsideLines, see above

A real forced purchase from staging that broke both caps:

{
  "type": "purchase",
  "amount": { "amount": "600.00", "currency": "USD" },
  "date": "2026-09-05",
  "metadata": {
    "overLimit": true,
    "lineOverLimitAmount": { "amount": "600.00", "currency": "USD" },
    "overLimitAmount": { "amount": "550.35", "currency": "USD" }
  }
}

A purchase that fits is never marked, even when sent with force: true: the flag authorizes an exception, it does not declare one.

When the card cannot back the lines

Interest, late fees and other charges are charged to the card, never to a line. So the card can run out of room while every line still shows cupo: the lines are promising money the card no longer has. In the example above the card owes 1,616.77 of charges (outsideLines), and Flota Sur still shows 1,000 available that no purchase will be able to use (unbacked).

unbacked > 0 is the warning. The ways out:

  1. Register a payment — it shrinks the card's debt.
  2. Raise the card's limit.
  3. Reduce the lines so the cupo they show fits what the card can back: POST …/credit-lines/fit.

How fit is computed:

  • It takes only idle cupo, so no line drops below what it consumed.
  • It spreads the cut in proportion to each line's idle cupo; per-line rounding is settled on the line with the most idle, so the cuts add up to unbacked exactly.
  • closeSuggested: true marks a line with no spending whose whole cupo is unbacked. A limit cannot be zero, so fitting it means closing it — which fit never does on its own. Close it explicitly if you agree.

What POST …/credit-lines/fit guarantees:

  • The plan is recomputed by the server at the moment of the call, never taken from the request: between reading the suggestion and confirming it, a payment or a purchase may have changed it. Show the operator the fit from credit-relief, and the response tells you what was actually applied.
  • All the lines change in one transaction.
  • Each changed line emits card.credit-line.limit-updated with "reason": "fit-to-card", visible in the card's history.
  • Idempotent: on a card that already fits, it changes nothing.
  • It removes the warning, not an overdraft: a card over its limit stays over — and refusing purchases — until a payment lands or its limit grows. And unbacked stays above zero while a closeSuggested line is left open.
  • It works in any mode, like closing or lowering a line: it is a way out.

How payments free the lines

The company pays the card, but the caps live on the lines, so each payment also decides whose consumption it settled. The release is decided when the payment lands and stored on it — never recomputed later.

  • How much: the share of the card's debt the payment covered. Paying 50% of what the card owes frees 50% of what the lines consumed.
  • Whose: the plan's lineRestoration — proportional (default: every line in proportion to its consumption), oldest-first or newest-first.
  • Under creditRestoration: on-close, the freed cupo is usable again only after the next cut-off, the same as the card's.
  • Reversing the payment undoes its release.
  • A payment can name the line it frees: see Payments directed to a line.

Worked example — lines Norte (consumed 3,000) and Sur (consumed 1,000), the card owes 4,000, the company pays 2,000 (50%):

PolicyNorte freesSur frees
proportional1,500500
oldest-first (Norte spent first)2,0000
newest-first1,0001,000

Payments directed to a line

POST /v1/card-accounts/{id}/payments
{
  "referenceId": "pay-778",
  "amount": { "amount": "500.00", "currency": "USD" },
  "creditLineId": "6f1d6a1e-0000-4000-8000-000000000003"
}

creditLineId says whose cupo the payment frees. It does not change how the payment is imputed to the card: the card's statements are settled by the plan's paymentAllocation, exactly as without a line — so the order a regulator may require (charges-first) is always respected.

  1. The named line gets back the payment amount, capped by what it consumed and by what the card owed.
  2. Whatever is left is an ordinary payment over the remaining debt and frees the other lines by lineRestoration.

The release does not look at which purchases were paid

With charges-first, a payment of 500 can be imputed entirely to interest and late fees — not one peso to the line's purchases — and the line still gets 500 of cupo back. That is on purpose: a line measures cupo, not accounting debt, and the real debt (including those charges) stays on the card. If the card cannot back the freed cupo, credit-relief reports it as unbacked.

An undirected payment frees payment / owed of every line's consumption. Interest and late fees are in what the card owes but in no line, so they dilute it: with 11,216.77 owed and 9,600 of line consumption, a 500 payment frees about 428 spread across the lines. Directed to one line, the same 500 frees 500 on that line.

Rules: company mode only (422 card_mode_mismatch); the line must be on this card (404) and not closed (412; a disabled line is accepted). The payment keeps creditLineId, the statement document names the line on the payment row (byCreditLine stays consumption only), and reversing the payment undoes its release.

Retries and idempotency

Every write in this API is safe to retry. The rules:

OperationKeyA retry
Open a linereferenceId (optional, unique per tenant)returns the existing line with 200 instead of a second one
Purchase / paymentreferenceId (required)returns the existing transaction with 200
Reverse a purchase / a paymentone reversal per transactionreturns the existing reversal with 200
Change a line's limit or statusthe target valuesetting the value it already has is a no-op
Fit the linesthe server's planon a card that already fits, changes nothing

Two responses mean "try again":

  • 409 concurrent_modification with a Retry-After header (seconds): another operation changed the same card at the same time. Wait and repeat the same request.
  • A timeout or a 5xx: you do not know whether the write landed. Repeat it with the same referenceId — if it did land, you get the existing record back, never a duplicate.

Use a different referenceId per operation

The key is the identity of the operation. Reusing one for a different purchase or line returns the earlier record and silently ignores the new body. Derive it from your own id for that operation, never from a label.

Permissions

PermissionGrants
card.account.readlist lines, read credit relief
card.account.writeopen lines, change their limit and status, fit them to the card
card.purchase.createpurchases against a line

Error Handling

StatusWhen
400Malformed id or amount, currency different from the card's
404Unknown card or line, or a line that belongs to another card
412The line is not enabled (purchases), or is closed (changes)
409 concurrent_modificationConcurrent change on the same card; retry after Retry-After
422 card_mode_mismatchOpening lines or attributing a purchase outside company mode
422 credit_line_over_assignedThe live lines' limits would exceed the card's limit
422 card_over_limitRaising cupo while the card is over its limit
422 credit_line_requiredA purchase without creditLineId on a card that has live lines
422 insufficient_line_creditThe purchase does not fit the line (and was not forced); details.scope = line
422 insufficient_creditThe purchase does not fit the card (and was not forced), even if the line had room; details.scope = card
422 card_limit_below_assignedLowering the card's limit below what is assigned to its lines