Tutorial: Request and enroll a physical card

End-to-end flow for issuing a physical Cardda card to a user, from "approve the request" to "the user holds the plastic in their hand and the card is associated to their account".

The flow:

  1. The user (or an admin) creates a card_issuing_request.
  2. An admin approves it.
  3. Cardda mints a virtual card immediately and dispatches a physical card to the shipping address.
  4. When the physical card arrives, the user activates it from the dashboard, which calls confirm_card.

1. Create the request

curl -X POST 'https://api.cardda.com/v1/card_issuing_requests' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "USER_UUID",
    "kind": "physical",
    "shipping_address": {
      "street": "Av. Apoquindo 4501",
      "city": "Las Condes",
      "region": "Santiago",
      "postal_code": "7550000",
      "country": "CL"
    },
    "spending_limit": { "amount": 500000, "currency": "CLP", "period": "monthly" },
    "label": "Sales — Maria Perez"
  }'
{
  "id": "request-uuid",
  "status": "pending_approval",
  "user_id": "USER_UUID",
  "kind": "physical",
  ...
}

2. Approve

Approval requires a company member with cards.approve permission (check via GET /v1/permissions).

curl -X PATCH 'https://api.cardda.com/v1/card_issuing_requests/REQUEST_ID/approve' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"

To deny instead:

curl -X PATCH 'https://api.cardda.com/v1/card_issuing_requests/REQUEST_ID/decline' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Out of policy — requested limit exceeds department cap" }'

After approval, Cardda creates a vendor_card. The status moves through:

vendor_card.statusMeaning
pending_issueCardda is talking to the issuer (PLH / Pomelo / Increase).
active_virtualVirtual card is usable; physical card is being printed/shipped.
shippedPhysical card mailed; tracking number available on shipping_metadata.
activePhysical card activated and in use.
closedCard revoked.

3. Track shipping

curl 'https://api.cardda.com/v1/vendor_cards/VENDOR_CARD_ID' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"
{
  "id": "vendor-card-uuid",
  "status": "shipped",
  "shipping_metadata": {
    "carrier": "Chilexpress",
    "tracking_number": "CL-12345678",
    "estimated_delivery": "2026-05-03"
  },
  "last4": "4242",
  ...
}

4. Activation by the cardholder

When the physical card arrives, the cardholder confirms their identity by sending the card's last 4 digits and CVV. Your front-end posts this to:

curl -X PATCH 'https://api.cardda.com/v1/vendor_cards/confirm_card' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor_card_id": "vendor-card-uuid",
    "last4": "4242",
    "cvv": "123"
  }'

After this call, status flips to active and the card can be used in person.

Set / change the PIN

curl -X PATCH 'https://api.cardda.com/v1/vendor_cards/VENDOR_CARD_ID/pin' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{ "pin": "0420" }'

Retrieve sensitive details (PAN / CVV) for a TPV widget

For PCI compliance, Cardda never returns the full PAN through the regular GET. Instead, you request a single-use, short-lived token and exchange it on the issuer's TPV widget:

curl 'https://api.cardda.com/v1/vendor_cards/VENDOR_CARD_ID/retrieve' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"
{
  "tpv_token": "ey...",
  "tpv_url": "https://tpv.plh.com/widget?token=ey...",
  "expires_at": "2026-04-29T18:05:00Z"
}

Embed tpv_url in an iframe — the cardholder sees their PAN and CVV without your servers ever touching them.

Common pitfalls

  • Wrong country in shipping address. Cardda issues in CL, MX, and PE. An address in another country is rejected at the approve step.
  • spending_limit smaller than a single legitimate purchase. The card declines silently at the merchant. Use the period: monthly semantics with reasonable headroom.
  • Cardholder does not have a verified phone. PLH-issued cards require the user's phone to be verified for SMS step-up. Check users.phone_verified before submitting the request.

Related