Skip to content

How to Manage Card Products

A card product is the offering an issuer puts in front of its customers — "Classic Debit", "Gold Credit". It is set up once and reused for every card issued under it, so this guide covers work you do rarely, and Create a Card covers the work you do per cardholder.

Everything here belongs to the Issuer API. Read QPay Issuer Core first for how the pieces relate.

Time to complete: ~25 minutes
API: Issuer API — base path /api/issuer


Prerequisites

  • An access token for an issuer tenant. See Authentication.
  • The product and layout roles listed in the step table below. A client holding manage-product and manage-layout can do everything in this guide.
export TOKEN="eyJhbGci..."
export BASE="https://sandbox.qpay.quecto.com.br"
export ISSUER="$BASE/api/issuer"

The product model

flowchart TD
    T[Card template<br/>owned by your processor] --> P[Card product<br/>name, description]
    P --> L1[Layout A<br/>design + embossing facts]
    P --> L2[Layout B]
    L1 --> E1[Embosser 1 — approved<br/>priority 0]
    L1 --> E2[Embosser 2 — approved<br/>priority 1]
    L2 --> E3[Embosser 1 — pending]

Three rules follow from that shape, and they explain most of the errors you can hit:

  • A layout belongs to one product. There is no shared layout catalogue — POST /api/issuer/card-products/{product_id}/layouts is the only way to create one, and a card may only use a layout of its own product.
  • An embosser is certified per layout, not per product. The same bureau producing two designs is two links, each approved on its own.
  • The currency is inherited, not chosen. It comes from the BINs on the product's card template, and it is what the ledger checks a card against. You pick it by picking the template.

Overview

flowchart TD
    A["1 — Confirm processor membership"] --> B["2 — Choose a card template"]
    B --> C["3 — Create the product"]
    C --> D["4 — Add a layout"]
    D --> E["5 — Describe the artwork (optional)"]
    E --> F["6 — Export product parameters"]
    F --> G["7 — Register the embosser"]
    G --> H["8 — Approve the link, after homologation"]
    H --> I["9 — Set the embosser priority"]
    I --> J["10 — Verify the product is issuable"]

Each box above is one step below. What each one leaves behind, and the role it needs:

Step Endpoint What it creates Role
1 — Confirm processor membership POST /processors A membership request — the processor answers it request-processor-membership (view-processor-membership to read only)
2 — Choose a card template GET /card-templates Nothing — read only create-product or manage-product
3 — Create the product POST /card-products The product create-product or manage-product
4 — Add a layout POST /card-products/{id}/layouts A layout under the product create-layout or manage-layout
5 — Describe the artwork (optional) POST /card-layout-assets, PUT …/graphics Artwork assets and the layout's graphic specification update-layout or manage-layout
6 — Export product parameters GET /card-products/{id}/export Nothing — a read-only projection manage-product
7 — Register the embosser POST …/layouts/{id}/embossers A ProductEmbosser link, not yet approved update-product or manage-product
8 — Approve the link PUT …/embossers/{id}/approve The approval on that link update-product or manage-product
9 — Set the embosser priority PUT …/embossers/priority The priority order among approved links update-product or manage-product
10 — Verify the product is issuable GET /card-products/issuing-data Nothing — read only view-product or manage-product

Step 1 — Confirm your processor membership

Card templates and BINs belong to a processor, so an issuer can only build products once a processor has accepted it. Check what you already have:

curl -s $ISSUER/processors \
  -H "Authorization: Bearer $TOKEN" | jq .

If the list is empty, find a processor open to requests and ask to join. Only the processor can turn the request into an active membership:

curl -s $ISSUER/processor-directory \
  -H "Authorization: Bearer $TOKEN" | jq .

curl -s -X POST $ISSUER/processors \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"processor_id": 1}' | jq .

The listing returns every relationship in any state — active, still pending, or declined with the processor's reason — so it is also where you check back afterwards.

You may already be a member

An issuer tenant created by a processor is already attached to it, in which case Step 1 is a confirmation rather than work. Request a membership only when the listing comes back empty, or when you want to work with a second processor.


Step 2 — Choose a card template

A card template is your processor's pre-certified configuration for a BIN range: the EMV applications, their AIDs and service codes, the DOLs, and the embossing restrictions your layouts must respect. You do not create one — you pick one.

curl -s $ISSUER/card-templates \
  -H "Authorization: Bearer $TOKEN" | jq .

Example response:

[
  {
    "id": 7,
    "name": "UnionPay Classic",
    "description": "Standard UnionPay debit template",
    "iin": "62270000",
    "currency_numeric_code": 986,
    "applications": [
      {
        "name": "UnionPay Debit",
        "aid": "A0000003330101",
        "service_code": "201",
        "service_code_usage": "International interchange, ICC, Normal authorization, No restrictions",
        "bin": "6227000000"
      }
    ]
  }
]
export TEMPLATE_ID=7
Query parameter When to use it
processor_id Required only once you work with more than one processor — the catalogues are not interchangeable, so QPay will not merge them
currency_numeric_code Optional ISO 4217 numeric filter (986 BRL, 840 USD); omit for every currency

The template fixes the currency

currency_numeric_code is the currency a product built on this template inherits, and it cannot be changed afterwards — a product with the wrong currency has to be replaced, not edited. A 0 means no BIN resolves on the template; a product built on it will be rejected by the ledger, so ask your processor to finish configuring it first.


Step 3 — Create the product

curl -s -X POST $ISSUER/card-products \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"card_template_id\": $TEMPLATE_ID,
    \"name\": \"Classic Debit\",
    \"description\": \"Standard UnionPay debit card\"
  }" | jq .

Example response:

{ "product_id": 101 }
export PRODUCT_ID=101
Field Required Notes
card_template_id Yes From Step 2. Cannot be changed afterwards
name Yes Your customer-facing product name
description Yes Free text
processor_id Only with several processors Inferred while you work with exactly one. The product's cards are issued and authorized under this processor, and it is fixed at creation

A new product is active, and has no layout and no embosser — it cannot issue a card yet.


Step 4 — Add a layout

A layout is one design of the product: what the plastic looks like and the two facts an embosser and the card network care about — whether the PAN is printed on the face, and where the cardholder name sits. A product can carry several, and cards choose between them at creation.

curl -s -X POST $ISSUER/card-products/$PRODUCT_ID/layouts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "layout": "standard-blue-v1",
    "prints_pan_on_face": false,
    "cardholder_name_zone": "bottom_left"
  }' | jq .

Example response:

{ "layout_id": 15 }
export LAYOUT_ID=15
Field Notes
layout The layout identifier agreed with your embosser — it maps to artwork and print specifications on their side
prints_pan_on_face Rejected if the card template sets disallow_pan_on_face
cardholder_name_zone Must equal the template's cardholder_name_position when the template requires one

The template's restrictions are enforced here, not at card creation

A layout that violates them is refused at POST, which is the cheap place to find out. If you get "disallowed by card template", the design has to change or the product needs a different template.

List, read, update and delete layouts under the same path:

curl -s $ISSUER/card-products/$PRODUCT_ID/layouts \
  -H "Authorization: Bearer $TOKEN" | jq .

curl -s -X PUT $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"layout": "standard-blue-v2", "prints_pan_on_face": false, "cardholder_name_zone": "bottom_left"}'

curl -s -X DELETE $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID \
  -H "Authorization: Bearer $TOKEN"

Step 5 — Describe the artwork (optional)

Two flags cannot describe a card. A layout can carry a full graphic specification — palette, artwork placement, and where each personalisation field is printed — so the design travels with the product instead of by email, and the certification export can carry it.

Upload each piece of artwork first. Assets belong to the issuer and are reused across layouts:

curl -s -X POST $ISSUER/card-layout-assets \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"kind\": \"network_mark\",
    \"mime\": \"image/png\",
    \"bytes_base64\": \"$(base64 -w 0 unionpay-mark.png)\"
  }" | jq .

kind is one of issuer_logo, network_mark, background or hologram_overlay; mime one of image/png, image/jpeg or image/svg+xml. The limit is 2 MB before encoding. Keep the asset_id from the response — the specification references artwork by id.

Then write the specification. archetype supplies the default geometry, and placements refer to its named zones — fetch the catalogue of archetypes, zones and reserved areas from GET /api/issuer/card-layout-geometry rather than guessing coordinates:

curl -s -X PUT $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID/graphics \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "spec_version": 1,
    "archetype": "horizontal-chip-left",
    "palette": {
      "background": { "type": "solid", "color": "#0B1220" },
      "foreground": "#FFFFFF"
    },
    "assets": {
      "network_mark": {
        "asset_id": 3,
        "placement": { "zone": "bottom_right" }
      }
    },
    "zones": {
      "pan":  { "placement": { "zone": "back_pan_strip" } },
      "name": { "placement": { "zone": "bottom_left" } }
    }
  }' | jq .

Render it to check it before anyone prints it:

curl -s $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID/preview.svg \
  -H "Authorization: Bearer $TOKEN" -o preview.svg

GET the same /graphics path to read the specification back, and DELETE it to return the layout to the two-flag form. The specification is a peer of the layout, not a replacement: prints_pan_on_face and cardholder_name_zone stay authoritative for certification.


Step 6 — Export product parameters

The export is the specification for setting up an embossing product at the embosser — the parameters their physical card production pipeline must follow to produce your card. An embosser does not read your product out of QPay; it configures a job of its own from these parameters, and everything it prints, encodes and personalises is driven by them.

It resolves the card template and the layouts into one read-only payload, and it serves two audiences:

  • An embosser already integrated with QPay — the parameters are what you feed back into their module so its production setup matches the product QPay will send cards for.
  • An external bureau with no QPay integration — the parameters are what you negotiate and homologate with. The payload is self-contained by design, so a party with no credentials on the platform can still work from it.

Either way the outcome is the same: the embosser sets up its embossing product, homologates it against these parameters, and returns their own identifier for it — which is what Step 7 records.

curl -s "$ISSUER/card-products/$PRODUCT_ID/export?assets=inline" \
  -H "Authorization: Bearer $TOKEN" -o product-parameters.json
assets Contents
manifest (default) Asset metadata and SHA-256 hashes, no bytes — the on-screen preview
inline The same plus every asset base64-encoded — the self-contained hand-off, since the recipient has no credentials to fetch them

The payload carries the product's scheme network, each layout (with its graphic specification and a spec_fingerprint), the EMV application parameters — AID, service code, CDOL1, CDOL2, TDOL, UDK tags — and the deduplicated assets. Nothing is persisted: it is a projection of the product as it stands right now. Record the spec_fingerprint your embosser approved, because a later mismatch is how you find out the artwork moved after homologation.


Step 7 — Register the embosser

Homologation happens off-platform. When it succeeds the embosser gives you their identifier for the certified embossing product; you record it against the layout:

curl -s $ISSUER/embossers \
  -H "Authorization: Bearer $TOKEN" | jq .
[ { "embosser_id": 3, "name": "Acme Card Bureau" } ]
curl -s -X POST $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID/embossers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"embosser_id": 3, "embosser_reference_id": "ACME-UPI-CLASSIC-BLUE"}' | jq .

Example response:

{
  "id": 44,
  "product_id": 101,
  "embosser_id": 3,
  "embosser_reference_id": "ACME-UPI-CLASSIC-BLUE"
}

The id — 44, not 3 — is the ProductEmbosser link. Keep it: the next two steps and card creation address the link, not the bureau.

export PRODUCT_EMBOSSER_ID=44

A new link is unapproved and cannot produce a card. Approving it is you recording that homologation is done — the embosser has no endpoint to approve itself, and QPay does not infer it.

curl -s -X PUT \
  $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID/embossers/$PRODUCT_EMBOSSER_ID/approve \
  -H "Authorization: Bearer $TOKEN" -o /dev/null -w '%{http_code}\n'

{embosser_id} means two different things on these paths

On approve, the last path segment is the ProductEmbosser id from the add response (44 above). On remove, the same-looking segment is the embosser's own id (3). Approving with 3 fails with embosser not found; removing with 44 silently removes nothing.

To detach a bureau from a layout — using the embosser id:

curl -s -X DELETE \
  $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID/embossers/3 \
  -H "Authorization: Bearer $TOKEN"

Step 9 — Set the embosser priority

Once a layout has more than one approved bureau, priority decides which one produces a card when the request does not name one. Lower is preferred, and the order you send is the order applied:

curl -s -X PUT \
  $ISSUER/card-products/$PRODUCT_ID/layouts/$LAYOUT_ID/embossers/priority \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"product_embosser_ids": [44, 45]}'

The ids are ProductEmbosser ids again, highest priority first. This is the lever for moving volume between bureaus without touching your card-creation code — a card issued tomorrow just picks up the new head of the list.


Step 10 — Verify the product is issuable

The issuing-data endpoint answers exactly one question: what can be issued right now? It lists only active products that have at least one layout with an approved embosser, so a product missing from it is a product that will fail at card creation.

curl -s $ISSUER/card-products/issuing-data \
  -H "Authorization: Bearer $TOKEN" | jq .
[
  {
    "product_id": 101,
    "name": "Classic Debit",
    "description": "Standard UnionPay debit card",
    "currency_numeric_code": 986,
    "layouts": [ { "layout_id": 15, "prints_pan_on_face": false } ],
    "credit_application_aids": []
  }
]

credit_application_aids lists the product's CREDIT applications. Each one needs an approved limit when the card is created on QPay's ledger — see Use the QPay Ledger. An empty array means the product is debit-only.

You are done. Go to Create a Card.


Day-2 product management

Read the full product, including its layouts and every embosser link with its approval state and priority:

curl -s $ISSUER/card-products/$PRODUCT_ID \
  -H "Authorization: Bearer $TOKEN" | jq .

curl -s $ISSUER/card-products \
  -H "Authorization: Bearer $TOKEN" | jq .

Rename, re-describe, or take a product off sale. Deactivating is the way to stop new issuance: POST /api/issuer/cards/create refuses an inactive product, while cards already issued keep working — their authorization path does not consult the product's active flag.

curl -s -X PUT $ISSUER/card-products/$PRODUCT_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Classic Debit", "description": "Retired offering", "is_active": false}' | jq .

Delete only a product that never issued a card — the record is what the authorization path resolves a card's template through:

curl -s -X DELETE $ISSUER/card-products/$PRODUCT_ID \
  -H "Authorization: Bearer $TOKEN"

Prefer a new product over editing a live one

Name and description are free to change. Everything a card actually depends on — template, currency, and therefore the BIN — is fixed at creation by design. A new offering is a new product, and the old one is deactivated once its cards expire.