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.
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 anaccount_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_typeappears 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.
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)
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 currency — 155375 is $1,553.75.
Two absences are normal and worth handling before they show up in support tickets:
statement_due_dateis 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
0means something different from absent: a paid-off balance and an unknown balance must not render identically.
What the account earns
For an authenticated customer, read the categories attached to the account you already resolved:Response (200)
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:
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)
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.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:
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.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)
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. Sopurchases, 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.
The statement PDF link is short-lived
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
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_READallowlisted 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_ratevalues 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
0in your rendering -
statement_due_dateabsent handled as “no payment due yet” -
pdf_urlre-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 forselected: falseandimage_orientationhonoured - 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_tokenback verbatim asstarting_after