Cards
Overview
The API provides virtual card issuance tied to authenticated users. Each card has a full lifecycle managed through a status state machine, with support for freezing, reporting lost or stolen cards, and configuring funding sources.
Cards are:
- User-scoped — all card operations derive the user from the authentication token
- Status-tracked — every status change is recorded as an auditable event with who initiated it
- Wallet-linked — cards can be linked to a user’s wallet for funding
- Hypermedia-driven — responses include
_links that reflect available actions based on the card’s current status
Quick Start
1. List your cards
curl /v0/cards/ \
-H "x-juno-jwt: <token>"
Response: 200 OK
{
"cards": [
{
"id": 42,
"last_four": "7890",
"brand": "visa",
"exp_month": 3,
"exp_year": 2027,
"cardholder_name": "Alex Rivera",
"card_type": "virtual",
"is_primary": true,
"linked_wallet_id": 15,
"created_at": 1700000000000,
"current_status": {
"id": 108,
"card_id": 42,
"status": "active",
"sub_status": "verified",
"changed_by": "self",
"created_at": 1700000060000
}
}
],
"total": 1,
"_links": {
"self": { "href": "/v0/cards" },
"user": { "href": "/v0/user/me" }
}
}
2. Get a specific card
curl /v0/cards/42 \
-H "x-juno-jwt: <token>"
The response includes _links that show which actions are available based on the card’s current status. An active card will include links to freeze, report lost, and report stolen.
3. Freeze the card
curl -X POST /v0/cards/42/freeze \
-H "x-juno-jwt: <token>"
Response: 200 OK
{
"id": 42,
"last_four": "7890",
"brand": "visa",
"card_type": "virtual",
"is_primary": true,
"created_at": 1700000000000,
"previous_status": {
"id": 108,
"card_id": 42,
"status": "active",
"sub_status": "verified",
"changed_by": "self",
"created_at": 1700000060000
},
"current_status": {
"id": 109,
"card_id": 42,
"status": "suspended",
"sub_status": "wallet_suspended",
"changed_by": "self",
"created_at": 1700000120000
},
"action": "freeze",
"_links": {
"self": { "href": "/v0/cards/42" },
"unfreeze": { "href": "/v0/cards/42/unfreeze", "method": "POST" },
"funding": { "href": "/v0/cards/42/funding" }
}
}
4. Unfreeze the card
curl -X POST /v0/cards/42/unfreeze \
-H "x-juno-jwt: <token>"
The card returns to active/reinstated status. All action responses include both previous_status and current_status so your UI can show the transition.
Card Types
| Type | Value | Description |
|---|
| Virtual | virtual | Digital card for online transactions |
| Physical | physical | Mailed plastic card |
| Metal | metal | Premium metal card |
Card Statuses
Cards use a two-level status system: a primary status and a sub_status that provides detail about why the card is in that state.
Primary Statuses
| Status | Meaning |
|---|
pending | Card has been created but is not yet usable |
active | Card is live and can process transactions |
suspended | Card is temporarily blocked — may be unfrozen depending on the reason |
closed | Card is permanently deactivated |
Sub-Statuses by Primary Status
Pending:
| Sub-Status | Description |
|---|
issuing | Card is being provisioned by the processor |
activation_required | Card is ready and waiting for user activation |
kyc_pending | Card issuance is blocked until KYC clears |
Active:
| Sub-Status | Description |
|---|
verified | Card is fully active after activation |
reinstated | Card was unfrozen and returned to active use |
Suspended:
| Sub-Status | Description |
|---|
wallet_suspended | User froze the card voluntarily |
lost | User reported the card as lost |
stolen | User reported the card as stolen |
fraud_suspected | System flagged the card for potential fraud |
compliance_review | Card is under compliance review |
Closed:
| Sub-Status | Description |
|---|
expired | Card reached its expiration date |
voluntary_close | User chose to close the card |
fraud_confirmed | Closed due to confirmed fraud |
compliance_close | Closed by compliance action |
replaced | Closed because a replacement card was issued |
Status State Machine
User-Allowed Transitions
Not all transitions can be initiated by the user. The following actions are available through the API:
| From Status | Available User Actions |
|---|
active/verified | Freeze, Report Lost, Report Stolen |
active/reinstated | Freeze, Report Lost, Report Stolen |
suspended/wallet_suspended | Unfreeze |
suspended/lost | Unfreeze |
pending/activation_required | Activate |
All other transitions (compliance actions, fraud escalation, expiration) are system-initiated and cannot be triggered through these endpoints.
Endpoints
List Cards
Returns all cards belonging to the authenticated user, each with its current status.
Response: 200 OK
Get Card
Returns a single card with its current status. Pass ?include_history=true to include the full status change history.
curl /v0/cards/42?include_history=true \
-H "x-juno-jwt: <token>"
Response: 200 OK
{
"id": 42,
"last_four": "7890",
"brand": "visa",
"exp_month": 3,
"exp_year": 2027,
"cardholder_name": "Alex Rivera",
"card_type": "virtual",
"is_primary": true,
"linked_wallet_id": 15,
"created_at": 1700000000000,
"current_status": {
"id": 109,
"card_id": 42,
"status": "suspended",
"sub_status": "wallet_suspended",
"changed_by": "self",
"created_at": 1700000120000
},
"status_history": [
{
"id": 109,
"card_id": 42,
"status": "suspended",
"sub_status": "wallet_suspended",
"changed_by": "self",
"created_at": 1700000120000
},
{
"id": 108,
"card_id": 42,
"status": "active",
"sub_status": "verified",
"changed_by": "self",
"created_at": 1700000060000
},
{
"id": 107,
"card_id": 42,
"status": "pending",
"sub_status": "activation_required",
"changed_by": "system",
"created_at": 1700000030000
}
],
"_links": {
"self": { "href": "/v0/cards/42" },
"unfreeze": { "href": "/v0/cards/42/unfreeze", "method": "POST" },
"user": { "href": "/v0/user/me" },
"transactions": { "href": "/v0/transactions?card_id=42" },
"funding": { "href": "/v0/cards/42/funding" },
"history": { "href": "/v0/cards/42?include_history=true" }
}
}
The status_history array is ordered newest-first (most recent status change at index 0).
Activate Card
POST /v0/cards/{card_id}/activate
Activates a card that is in pending/activation_required status. Transitions it to active/verified.
Response: 200 OK — returns a CardActionResponse with action: "activate".
Freeze Card
POST /v0/cards/{card_id}/freeze
Temporarily suspends an active card. The card moves to suspended/wallet_suspended and can be unfrozen later.
Response: 200 OK — returns a CardActionResponse with action: "freeze".
Unfreeze Card
POST /v0/cards/{card_id}/unfreeze
Restores a frozen or lost card to active use. Transitions it to active/reinstated.
Response: 200 OK — returns a CardActionResponse with action: "unfreeze".
Report Lost
POST /v0/cards/{card_id}/lost
Reports a card as lost. The card moves to suspended/lost. It can still be unfrozen if found.
Response: 200 OK — returns a CardActionResponse with action: "lost".
Report Stolen
POST /v0/cards/{card_id}/stolen
Reports a card as stolen. The card moves to suspended/stolen and the system automatically escalates it to suspended/fraud_suspected for investigation.
Reporting a card as stolen triggers an automatic fraud escalation. Unlike freeze or lost, the resulting fraud_suspected status cannot be reversed by the user — only system or compliance actions can reinstate or close the card.
Response: 200 OK — returns a CardActionResponse with action: "stolen". The current_status reflects the escalated fraud_suspected sub-status.
Action Response Shape
All action endpoints (freeze, unfreeze, lost, stolen, activate) return the same shape:
| Field | Type | Description |
|---|
id | integer | Card ID |
last_four | string | Last four digits of the card number |
brand | string | Card brand (e.g. visa, mastercard) |
card_type | string | One of: virtual, physical, metal |
is_primary | boolean | Whether this is the user’s primary card |
created_at | integer | Card creation timestamp (epoch ms) |
previous_status | object | Status before the action |
current_status | object | Status after the action |
action | string | The action that was performed |
_links | object | Available actions based on the new status |
Card Funding
Each card has a funding configuration that determines where the card draws funds from when processing transactions.
Get Funding Configuration
GET /v0/cards/{card_id}/funding
curl /v0/cards/42/funding \
-H "x-juno-jwt: <token>"
Response: 200 OK
{
"card_id": 42,
"source_type": null,
"wallet_id": 15,
"fiat_account_id": null,
"configured": true,
"_links": {
"self": { "href": "/v0/cards/42/funding" },
"card": { "href": "/v0/cards/42" },
"user": { "href": "/v0/user/me" },
"card-transactions": { "href": "/v0/transactions?card_id=42" },
"update": { "href": "/v0/cards/42/funding", "method": "PUT" },
"wallet": { "href": "/v0/wallets/15" },
"wallet-transactions": { "href": "/v0/transactions?wallet_id=15" },
"wallet-assets": { "href": "/v0/wallets/15/assets" }
}
}
| Field | Type | Description |
|---|
card_id | integer | The card this funding configuration belongs to |
source_type | string or null | Funding source type: "wallet" or "fiat" |
wallet_id | integer or null | ID of the linked wallet (if source is a wallet) |
fiat_account_id | string or null | ID of the linked bank account (if source is fiat) |
configured | boolean | Whether a funding source has been set up |
Update Funding Configuration
PUT /v0/cards/{card_id}/funding
curl -X PUT /v0/cards/42/funding \
-H "x-juno-jwt: <token>" \
-H "Content-Type: application/json" \
-d '{
"source_type": "wallet",
"wallet_id": 15
}'
Request body:
| Field | Type | Required | Description |
|---|
source_type | string | Yes | "wallet" or "fiat" |
wallet_id | integer | No | Required when source_type is "wallet" |
fiat_account_id | string | No | Required when source_type is "fiat" |
Response: 200 OK
Integration Patterns
Building a Card Management UI
- Fetch the user’s cards with
GET /v0/cards/
- For each card, read
current_status to determine its state
- Use the
_links in each card response to discover available actions — active cards will include freeze, lost, and stolen links; frozen cards will include unfreeze
- After performing an action, use the returned
current_status and _links to update the UI immediately
The _links in card responses change based on the card’s current status. Rather than hardcoding which actions are available for each status, read the links from the response:
card = GET /v0/cards/42
if "freeze" in card._links:
show_freeze_button(card._links.freeze.href)
if "unfreeze" in card._links:
show_unfreeze_button(card._links.unfreeze.href)
if "activate" in card._links:
show_activate_button(card._links.activate.href)
This ensures your UI stays in sync with the backend’s transition rules without duplicating the state machine logic.
Status History for Audit
Use ?include_history=true on the get card endpoint to display a timeline of status changes. Each entry includes changed_by to distinguish user-initiated actions from system or compliance events.
status_history[].changed_by values:
"self" — user initiated the action
"admin" — platform administrator
"ops" — operations team
"system" — automated process
"compliance" — compliance team
Error Handling
All error responses follow RFC 7807 Problem Details format:
{
"type": "https://api.sumvin.com/errors/crd-404-001",
"title": "Card Not Found",
"status": 404,
"detail": "Card 999 not found",
"instance": "/v0/cards/999",
"error_code": "CRD-404-001"
}
| Status | Error Code | Meaning |
|---|
| 400 Bad Request | CRD-400-001 | Invalid status transition — the card cannot move from its current status to the requested one |
| 401 Unauthorized | — | Missing or invalid authentication token |
| 403 Forbidden | CRD-403-001 | Card does not belong to the authenticated user |
| 403 Forbidden | CRD-403-002 | Action is not allowed for the current card status (e.g. user trying a system-only transition) |
| 404 Not Found | CRD-404-001 | Card with the given ID does not exist |
| 404 Not Found | CRD-404-002 | No status record found for the card |
Reference Tables
Card Fields
| Field | Type | Description |
|---|
id | integer | Unique card identifier |
last_four | string | Last four digits of the card number |
brand | string | Card brand: visa, mastercard, amex, discover, platform, unknown |
exp_month | integer | Expiration month (1-12) |
exp_year | integer | Expiration year (4-digit) |
cardholder_name | string or null | Name printed on the card |
card_type | string | One of: virtual, physical, metal |
is_primary | boolean | Whether this is the user’s primary card |
linked_wallet_id | integer or null | ID of the linked funding wallet |
created_at | integer | Creation timestamp (epoch ms) |
Status Fields
| Field | Type | Description |
|---|
id | integer | Status record ID |
card_id | integer | Card this status belongs to |
status | string | Primary status: pending, active, suspended, closed |
sub_status | string or null | Detailed sub-status within the primary status |
changed_by | string | Who initiated the change: self, admin, ops, system, compliance |
created_at | integer | When the status change occurred (epoch ms) |
Card Brands
| Value | Description |
|---|
visa | Visa |
mastercard | Mastercard |
amex | American Express |
discover | Discover |
platform | Platform-branded card |
unknown | Brand not determined |
Next Steps