Managing a Card

Activate, block, unblock, close, rename a card and change its spending limits.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain the corporation token
Issuing a CardWhere {cardId} comes from
Card DetailsReading allowed_actions before acting

Overview

Card management is status-driven. Every action is evaluated against the card's current status before it
runs, and the same evaluator produces the allowed_actions array on each card in
GET /api/v1/cards. Read allowed_actions and act on what it lists — it is the API's own answer to
what is possible right now.


Status Lifecycle

Requested → NotActivated → Active
                             ↓
                          Blocked ←→ Active
                             ↓
                           Closed
StatusDescription
RequestedThe order is with the processor. Nothing can be done to the card yet
NotActivatedIssued and awaiting activation. Plastic cards land here
ActiveUsable
BlockedTemporarily suspended. Reversible
ClosedPermanently closed. Terminal

A virtual card goes straight from Requested to Active.

Action Gates

ActionAllowed inRefused in
ActivateNotActivatedRequested (Card is not issued yet), Active, Blocked, Closed (Card is already activated)
BlockActiveRequested, NotActivated, Closed (Card status does not allow blocking), Blocked (Card is already blocked)
UnblockBlockedEverything else (Card is not blocked)
CloseActive, BlockedRequested, NotActivated (Card status does not allow closing), Closed (Card is already closed)
SetLimitActiveEverything else (Card status does not allow limit changes)
GetDetails, GetCvv, GetPinActiveEverything else

Renaming a card is the one operation with no status gate — a card in any status can be renamed.


Activate a Card

PUT /api/v1/cards/{cardId}/activate

Requires CardSu or CardManage. Only applies to physical cards; the two body fields prove the caller
is holding the card that arrived.

Path parameters:

ParameterDescription
{cardId}The card's id from GET /api/v1/cards

Request body:

{
  "card_number_last4": "5678",
  "expiry_date": "01/2028"
}
FieldTypeRequiredDescription
card_number_last4stringYesLast 4 digits of the printed card number. Must match ^\d{4}$
expiry_datestringYesExpiry date printed on the card, in MM/YYYY. Must match ^(0[1-9]|1[012])/\d{4}$

Response:

{}

The field is card_number_last4 — no underscore before the 4 — while the card record reports the
same value as card_data.card_number_last_4. The two spellings are not interchangeable.

expiry_date is four-digit year, 01/2028, not 01/28. A two-digit year fails the pattern.


Block and Unblock

PUT /api/v1/cards/{cardId}/block
PUT /api/v1/cards/{cardId}/unblock

Requires CardSu or CardManage. Neither takes a body.

Response:

{}

Blocking stops all transactions on the card and is reversible. Use it for a lost card before a
replacement is decided; use close when the card is gone for good.


Close a Card

PUT /api/v1/cards/{cardId}/close

Requires CardSu or CardManage. Takes no body.

Response:

{}

Closing is permanent. There is no reopen endpoint, and a Closed card cannot be blocked, unblocked,
re-limited or read. Closing is also what frees a slot against the issuance ceiling — 30 virtual and
15 plastic cards per corporation.


Rename a Card

PUT /api/v1/cards/{cardId}/name

Requires CardSu or CardManage.

Request body:

{
  "name": "Marketing team card"
}
FieldTypeRequiredDescription
namestringYesNew display name. Must match ^[a-zA-Z0-9]+[a-zA-Z0-9\-':+#& ]{1,50}$

Response:

{}

This changes card_data.card_name only — the display label. It does not change name_on_card, which is
embossed at issuance and cannot be changed afterwards.


Change Spending Limits

PUT /api/v1/cards/{cardId}/limit

Requires CardSu or CardLimitManage. The card must be Active.

Request body:

{
  "daily_limit": 1000.00,
  "monthly_limit": 10000.00,
  "transaction_limit": 500.00
}
FieldTypeRequiredDescription
daily_limitnumberNoMaximum spend per day. -1 disables it
monthly_limitnumberNoMaximum spend per calendar month. -1 disables it
transaction_limitnumberNoMaximum per single transaction. -1 disables it
limitnumberNoObsolete alias for daily_limit. When both are sent, limit wins

Response:

{}

This call replaces the whole limit set, it does not patch it. Any of the three limits you omit is
set to -1 — disabled. Sending only daily_limit silently removes the monthly and per-transaction
limits. Always read the current limits from GET /api/v1/cards and send all three values back,
changing the one you mean to change.

The lifetime limit is set to -1 on every call and cannot be configured through this API.

Reading Current Limits

{
  "limit": {
    "daily_limit": 1000.0,
    "daily_usage": 200.0,
    "monthly_limit": 10000.0,
    "monthly_usage": 2000.0,
    "transaction_limit": 500.0,
    "currency": "USD"
  }
}
FieldDescription
daily_limit / monthly_limitConfigured ceilings. -1 means no limit
daily_usage / monthly_usageSpent so far in the current period
transaction_limitPer-transaction ceiling
currencyISO 4217 currency the limits and usage are expressed in

Limit and usage changes are also delivered to
POST {your_webhook_base_url}/v2/webhooks/card-limits.

Code

// Read current limits first — omitted limits are disabled, not preserved
const cardsResponse = await fetch(`${baseUrl}/api/v1/cards`, {
  headers: { 'Authorization': `Bearer ${corporationToken}` }
});
const { data: cards } = await cardsResponse.json();
const current = cards.find((c) => c.id === cardId).limit;

await fetch(`${baseUrl}/api/v1/cards/${cardId}/limit`, {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    daily_limit: 2000.00,
    monthly_limit: current.monthly_limit,
    transaction_limit: current.transaction_limit
  })
});
# Read current limits first — omitted limits are disabled, not preserved
cards = requests.get(
    f"{base_url}/api/v1/cards",
    headers={"Authorization": f"Bearer {corporation_token}"},
).json()["data"]
current = next(c for c in cards if c["id"] == card_id)["limit"]

requests.put(
    f"{base_url}/api/v1/cards/{card_id}/limit",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={
        "daily_limit": 2000.00,
        "monthly_limit": current["monthly_limit"],
        "transaction_limit": current["transaction_limit"],
    },
)
// Read current limits first — omitted limits are disabled, not preserved
listReq, _ := http.NewRequest("GET", baseURL+"/api/v1/cards", nil)
listReq.Header.Set("Authorization", "Bearer "+corporationToken)

listResp, _ := http.DefaultClient.Do(listReq)
defer listResp.Body.Close()

var cardsResp struct {
    Data []CardResponse `json:"data"`
}
json.NewDecoder(listResp.Body).Decode(&cardsResp)
current := findCard(cardsResp.Data, cardId).Limit

body, _ := json.Marshal(map[string]float64{
    "daily_limit":       2000.00,
    "monthly_limit":     current.MonthlyLimit,
    "transaction_limit": current.TransactionLimit,
})

req, _ := http.NewRequest("PUT", baseURL+"/api/v1/cards/"+cardId+"/limit", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+corporationToken)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

Webhooks

Card Webhook

Endpoint: POST {your_webhook_base_url}/v2/webhooks/cards

Delivered on every card state change — issuance, activation, block, unblock, close.

The payload is the shared card shape and carries fields the Corporate API's own card list does not:
generation, balances, and lifetime_limit / lifetime_usage inside limit. Ignore them.

Card Limit Webhook

Endpoint: POST {your_webhook_base_url}/v2/webhooks/card-limits

Delivered when a limit or its usage changes.

See Webhooks for the delivery contract.


Error Handling

{
  "error_reason": "ErrorInvalidStatus",
  "error_description": "Action is not allowed",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "action_type", "details": "Block" },
    { "key": "value", "details": "Blocked" }
  ]
}

The action_type detail names the action and value names the card status that refused it.

Validation Errors (400)

Error ReasonDescriptionResolution
ErrorInvalidStatusCard is not issued yetThe card is Requested. Wait for the issuance webhook
ErrorInvalidStatusCard is already activatedThe card is past NotActivated
ErrorInvalidStatusCard status does not allow blockingOnly an Active card can be blocked
ErrorInvalidStatusCard is already blockedRead allowed_actions before acting
ErrorInvalidStatusCard is not blockedUnblock applies only to a Blocked card
ErrorInvalidStatusCard status does not allow closingA Requested or NotActivated card cannot be closed
ErrorInvalidStatusCard is already closedTerminal state
ErrorInvalidStatusCard status does not allow limit changesThe card must be Active
ErrorMissingFieldcard number last4 is requiredSend the last 4 printed digits
ErrorInvalidFieldInvalid format for card number last4Exactly 4 digits
ErrorMissingFieldexpiry date is requiredSend the printed expiry date
ErrorInvalidFieldInvalid format for expiry dateMM/YYYY with a four-digit year
ErrorMissingFieldname is requiredSend a display name
ErrorInvalidFieldInvalid format for nameMatch the card-name pattern

Permission Errors (403)

Error ReasonDescriptionResolution
ErrorPermissionDeniedUser does not have required permissionsStatus changes need CardManage; limits need CardLimitManage

Server Errors (500)

Error ReasonDescriptionResolution
ErrorGeneralFailed to update card limitThe limit service rejected the change. Retry
ErrorGeneralCard lookup failureThe {cardId} does not belong to this corporation

Did this page help you?