Skip to main content

Overview

Bring relevant account details into your native experience. A customer who holds your card checks their balance where they already are — in your app, on your account page — not by leaving for a servicing site. A handful of read-only endpoints are enough to make that page useful without building a servicing product: This guide walks the full sequence and the empty states worth handling before you ship.

Permissions

All four account servicing reads are gated on one scope your API key does not have by default.
A key that works for your other calls will return 403 here until two things happen: the scope is allowlisted for your product, and the key is rotated to pick it up. Allowlisting alone does not change an existing key. Ask your Imprint team to enable the scope, then rotate.
The card reads and product-wide reward categories read need no scope of their own, so you can render card art and product earning details before ACCOUNT_READ is enabled for you — useful for getting those parts of the page in place while the allowlisting is still in flight.

Resolving what to read

None of the other three account reads is addressable from a customer id alone: all take an account_id in the path, and the way to learn it is to list the customer’s accounts.
account_type is how you tell them apart, and the value matters to your UI: a SECUREDCREDITCARD has a customer-funded security deposit behind its credit line, so copy like “your credit limit was set by Imprint” is wrong for it. The reads that follow are identical for both — same fields, same conventions — so only the labelling changes. CREDITCARD and SECUREDCREDITCARD are the values a card program returns, and the field is omitted when the product’s type could not be determined. Three things about this read shape the code around it:
  • It is scoped to your program. An account the customer holds with another Imprint program is never returned, so you do not have to filter the result.
  • account_type appears here and not on the account itself. Carry it across if you render it; the per-account read does not repeat it.
  • A customer can hold more than one account. Most programs issue one, and taking the first entry is a reasonable start — but decide deliberately rather than by accident, because the day a customer has two, a page that silently shows one of them is wrong rather than incomplete.
An account_id is stable for the life of the account, so you can cache it against your own customer record after the first resolution and skip this call on later page loads. Re-list when the cached id starts answering 404.

Where the customer stands today

Response (200)
This is the live view: current_balance includes activity since the last cycle closed, and overdue_amount moves down as payments land. It is what a balance tile should render. as_of is when we calculated it — the instant these figures were true. Balances move between one read and the next, so if you cache or render this alongside your own data, label it with as_of rather than with the time you stored it, and use it to decide which of two reads is the later one. Every amount here is positive and says what the customer owes. A paid-off or in-credit account reads as 0, never a negative number. Amounts are integers in the smallest unit of currency155375 is $1,553.75. Two absences are normal and worth handling before they show up in support tickets:
  • statement_due_date is absent on an account in its first billing cycle. There is no closed statement to owe payment on yet. Render “no payment due yet”, not an empty date.
  • Any amount can be absent, and 0 means something different from absent: a paid-off balance and an unknown balance must not render identically.
Per-statement figures — the statement balance, the minimum due, interest — are not on this response. They live on the statements read.

What the account earns

For an authenticated customer, read the categories attached to the account you already resolved:
Response (200)
Use the account reward categories API on a servicing page: it anchors the result to the same customer and account as the balance and statements. It requires ACCOUNT_READ and returns 404 when the customer or account cannot be resolved. If you need to show earning categories before you know the customer or account — for example, on a product overview — use the product reward categories API instead:
The API key determines the product, so this read accepts no product, customer, or account identifier and needs no additional scope. Both endpoints return the same shape: categories contains standard transaction earning rules and special_categories contains active non-transaction offers such as a signup bonus. Treat earn_rate as a decimal string rather than a floating-point number. It is the number of reward units earned per one transaction-currency unit, while currency names the reward currency in which those units are denominated. Preserve the string’s configured precision when displaying it.

The card the customer is actually holding

A balance next to the customer’s own card art reads as their account. A balance next to a generic rectangle reads as a widget. Two calls get you the artwork, and neither needs a scope. What you want is the design actually on their card — not the program default, and not whatever they picked during apply, which may not be what was ultimately issued. Start from the customer’s cards, because the design belongs to the card rather than to the customer or the account:
Response (200)
Then read the design printed on the card you are rendering:
Response (200)
light_asset_url_path and dark_asset_url_path are absolute URLs, served from https://assets.imprint.co or https://app.imprint.co depending on where the artwork was uploaded. Render them directly against the customer’s light/dark appearance; do not prepend a host of your own. Unlike a statement pdf_url these are not pre-signed and do not expire, so they are safe to cache — though a design change swaps them, so prefer a modest TTL over caching forever. image_orientation tells you whether the artwork was drawn VERTICAL or HORIZONTAL; read it rather than assuming, or the asset ends up letterboxed or stretched in a frame built for the other one. Read the design per card, not per customer. A customer holding several cards can have a different design on each, so a page rendering one card should read that card’s design. Pair it with card.last4 from the list response so the customer can tell which of their cards the balance belongs to.
selected: false is a normal state, not an error. Some programs assign a default design asynchronously, so a freshly issued card can legitimately have no design resolved yet: the response is 200 with selected: false and no card_design. A loan or bank account payment method also returns 200 with selected: false — only CARD payment methods carry a design.Render your own placeholder in that case and re-read later. 404 means something different: this program has no such payment method.
Two notes on the list call. It returns every payment method type, so filter on type == "CARD" before looking for a design. And it takes an optional PCI_DETAILS_READ scope that adds full PAN, CVV, and expiry to the response — you do not need it here, and a card-art-and-balance page is better off without it.

Showing the rest of the design catalog

The read above answers “what is on this card”. To show the alternatives alongside it — a design picker in your servicing UI — you also need the program’s catalog:
Returns every card design available for your program rather than one card’s design. Also scope-free. Choose based on whether you want to show locked designs. customer_id is a hard filter, not a hint: designs above the customer’s loyalty tier are omitted from the response rather than flagged. For a servicing picker, customer_id is usually what you want — a customer offered a design they cannot select is a support ticket. If you deliberately want to show locked designs, omit customer_id and compare each design’s access_min_rank against the customer’s tier yourself; 0 means available to everyone. Only ACTIVE designs can be selected. If your program defines no designs of its own, the designs configured for your partner are returned instead.
loyalty_tier_name is currently always omitted, so a locked design cannot be labeled with the tier name it requires. Use access_min_rank and map it to your own tier copy.
Results are paginated — when has_more is true, pass next_page_token back as starting_after for the next page.

Rendering both together

A picker needs both calls: GET /v2/card_designs for the options and GET /v2/payment_methods/{payment_method_id}/card_design for the one currently on the card, matched on id to mark the current selection. The catalog alone cannot tell you which design the customer has, and the per-card read alone cannot tell you what else they could choose. One edge case to handle: the card’s current design may not appear in the catalog response. A design can be set to INACTIVE after it was printed, and a tier-gated design stays on the card if the customer’s tier later drops — in both cases the per-card read returns it while a customer_id-scoped catalog omits it. Render the current design from the per-card response rather than by looking its id up in the catalog, or it will silently disappear from your UI. See Application APIs for the same catalog read used during apply, where it is fetched without a customer_id for pre-signup screens — and where the design is selected, by passing card_design_id on the application. There is no partner endpoint that changes the design on an already-issued card.

Statement history

Response (200)
Statements are newest first, and a cycle appears here once it has closed — the cycle in progress is not a statement. A year of history (limit=12) is usually the right depth for an embedded list; limit accepts 1–100 and defaults to 10. When has_more is true, pass next_page_token back as starting_after. That token is opaque and is not a statement id; a value this endpoint did not issue answers 400 rather than serving page one. as_of here dates the read, not the cycle — period_end_date is when the cycle closed. Every row on a page carries the same value, and pdf_url expires an hour after it, so it is also the clock your link-refresh logic should work from.

Amounts are signed here, unlike on the account

These amounts follow the convention on a paper statement: what the customer was charged is positive, what was credited back to them is negative. So purchases, interest, and fees are positive while payments, credits, and refunds are negative. Nothing is clamped, because here the sign is the information — flattening it makes a payment indistinguishable from a month with no activity. Do not reuse your account-page formatter on these fields. The two responses deliberately disagree: /accounts/{account_id} answers “what do you owe”, and a statement answers “what happened in this cycle”. As on the account, only the lines a cycle had activity on are reported. A quiet month omits refunds rather than sending 0. pdf_url is a pre-signed link to the same document the cardholder sees. It expires one hour after the response that carried it, so treat it as a click target, not something to store: a URL persisted in your database will stop working, and re-reading the statement is how you get a fresh one. Two practical consequences:
  • Keep the link fresher than its lifetime if the list can sit open. Re-reading the page every 15 minutes or so is enough; refreshing on click is not, because awaiting a request takes the navigation out of the user’s gesture and browsers block the popup.
  • Redact it from logs. A pre-signed URL is a bearer credential for that document for the next hour. Logging the response verbatim publishes the statement to anyone who can read your logs.
pdf_url is also absent while the document is still rendering. A statement’s PDF is produced shortly after its cycle closes, so the newest entry can arrive before its document does; the rest of the statement is accurate. Render the row without a download link rather than hiding the row.

Handling the empty states

A servicing tile is one section of a page you own, so nothing here should be able to fail that page. Every way of having nothing to show has a defined answer, and it is worth mapping each to its own copy: Two of these are easy to conflate and shouldn’t be. A 404 means the account genuinely is not there; anything else means the read failed. And an empty data array from a readable account is not a failure at all — it is a real customer whose first cycle has not closed. The pattern that keeps this manageable: resolve the customer once, then let each section return “unavailable, because X” instead of an error. The account tile, the statement list, reward categories, and the card art share that resolution but fail independently, so one read timing out costs you that section and not the balance or the artwork.

A working sequence, end to end

Steps 3–5 are independent once step 2 resolves, so issue them concurrently. Step 6 does not depend on step 2 at all — the card art hangs off the customer, not the account — so it can run alongside step 2. Step 1 is skippable if you already hold the Imprint customer_id from account linking or a webhook. For a page that only needs product-wide earning categories, call GET /v2/rewards/rewards_categories without running this account-resolution sequence.

Checklist before you go to production

  • ACCOUNT_READ allowlisted for your product, and the API key rotated afterwards
  • Account reward categories used for servicing, or product reward categories used when no customer or account is available
  • Reward earn_rate values kept as decimal strings with their configured precision
  • Account amounts formatted as “what you owe” (always positive), statement amounts formatted with their sign preserved
  • Absent amounts distinguished from 0 in your rendering
  • statement_due_date absent handled as “no payment due yet”
  • pdf_url re-read rather than stored, and redacted from logs
  • A statement row renders without pdf_url
  • Card art read per payment method, filtered to type == "CARD", with a placeholder for selected: false and image_orientation honoured
  • If you render a design catalog, the current design comes from the per-card read rather than from a lookup in the catalog response
  • Each empty state in the table above has its own copy, and none of them fails the surrounding page
  • Pagination passes next_page_token back verbatim as starting_after