AfroPin Developers
Postman Sign in Get test keys

Selling PINs

Sell a PIN

Sell a new PIN to a customer. The face value is debited from your balance in the product currency, and the response carries the full PIN code, which is shown only here.

POST https://afropin.com/api/v1/reseller/pins

HMAC-signed request with a unique Idempotency-Key header. Authentication

With an SDK
PHP$afropin->reseller->pins->sell($params)
Node.jsafropin.reseller.pins.sell(params)
Pythonafropin.reseller.pins.sell(params)
Javaafropin.reseller().pins().sell(params)

Body parameters

catalog_id uuid Required

An active product id from Products you can sell.

amount numeric Required

Face value to load onto the PIN, in the product currency. For any_amount products it must be between min and max; for set_amounts products it must be one of the denominations.

external_reference string Required max 191

Your own reference for this sale, unique across all your issues and top-ups. A reused reference fails with duplicate_external_reference, so a retried request can never sell a second PIN.

first_name string Required max 255

Customer first name.

last_name string Required max 255

Customer last name.

email string Required email max 255

Customer email. The domain must accept mail (checked by DNS).

phone_code string Required with phone ISO2

Country of the phone number as an ISO 3166-1 alpha-2 code, for example NG.

phone string Required with phone_code

Customer phone number, valid for phone_code. Stored in E.164 format.

whatsapp boolean Optional

Whether codes we deliver to the customer also go to phone on WhatsApp. Defaults to true when a phone is given; send false to use email only. It applies when we deliver codes (handle_delivery); the customer can turn it off from their card page. Only live PINs get WhatsApp messages.

webhook_id uuid Optional

ID of one of your webhooks in the same mode. Events for this PIN (issued, topped up, expiry) are sent to it. Change it later with Change the webhook on a PIN.

handle_delivery boolean Optional

true to have us send the customer their code: now, and the new code or balance after every top-up, spend and new code for as long as the PIN lasts, by email (and WhatsApp with phone). Defaults to false: you deliver the code yourself. It works with or without a webhook_id. Change it later with Choose who delivers a PIN. How delivery works

store_id string Required if your account enforces purchase locations max 255

ID of the active purchase location making the sale, from Your stores. Optional unless purchase-location enforcement is turned on for your account.

keep_code boolean Optional

true when the customer chooses to keep one code: it then won't change after each spend or top-up, so they can share it, for example with their children. Only for products with allow_keep_code in the catalog; others fail with keep_code_not_allowed. Before sending it, show the customer this warning and get their agreement: "This card keeps the same code: it won't change after each spend or top-up. Anyone who has the code can spend the balance, at any time, until it runs out. If it's lost or shared by mistake, the money can't be recovered. You can get a new code at any time, which switches back to a new code after every spend." The response returns it as keep_code_notice. Giving the PIN a new code switches it back to a new code after each spend.

daily_limit numeric Optional

The most that can be spent from the PIN each day (midnight to midnight, Lagos time), in the product currency. Redemptions over what is left of it fail with pin_daily_limit_reached. Only for products with allow_daily_limit; others fail with daily_limit_not_allowed.

wallet_links boolean Optional

Send true to get a google_wallet_url and apple_wallet_url in the response: links your customer opens to add the PIN to Google Wallet or Apple Wallet. Same as calling Add to phone wallets after the sale.

Response fields

pin_id
The PIN ID. Use it to top up, activate, disable or look up the PIN.
pin_code
The 16-digit PIN code, formatted 0000-0000-0000-0000. We store only a one-way fingerprint of it, so this is the only time you can read it. Save it securely and give it to the customer, but do not log it: a lost code can never be recovered.
pin_mask
The code with the middle digits hidden, safe to show and store.
transaction_ref
ID of the credit to the PIN, as listed by List PIN transactions.
amount, balance
Amount loaded and the PIN balance afterwards (the same on issue).
status
active for a new PIN.
handle_delivery
Whether we deliver to the customer, as you sent it. When true we have sent them the code, so you don't need to.
keep_code, daily_limit, keep_code_notice
The customer's choices as saved. keep_code_notice is the warning to show the customer when keep_code is true, otherwise null; we also put it in their email and wallet pass.
google_wallet_url, apple_wallet_url
Only when you sent wallet_links. The wallet links for the customer; one is null if that wallet could not be reached. The PIN is sold either way, so fetch missing links later with Add to phone wallets.
expires_at
When the PIN expires: the issue time plus the product duration in days. Expired PINs cannot be topped up or re-activated.
We never store PIN codes
pin_code is the only time you will see this code. Save it securely before you respond to your customer: we keep only a one-way fingerprint of it, so a lost code can never be shown again. If your customer loses it, reissue the PIN to give it a new code; the balance is kept. Storing PIN codes
Your balance must cover the full amount. Your purchase commission is credited to your commission balance at the same time. In live mode you get an email when a wallet drops below its low-balance warning.
Requests from your account are processed one at a time. A request sent while another is still running fails with rate_limited (429). Wait a moment and retry with the same external_reference and a new Idempotency-Key.

Request

{
    "catalog_id": "2c3ec3c4-b729-4d40-9a0a-80e55d683ee6",
    "amount": 5000,
    "external_reference": "order-7f3a91",
    "first_name": "Ada",
    "last_name": "Okafor",
    "email": "[email protected]",
    "phone_code": "NG",
    "phone": "08031234567",
    "webhook_id": "3693382f-d2f0-4966-88f7-26d03c454940",
    "handle_delivery": false,
    "wallet_links": true
}

Response 200

{
    "ok": true,
    "data": {
        "pin_id": "a41cabf8-f667-43d3-9c0b-d28b39e18993",
        "webhook_id": "3693382f-d2f0-4966-88f7-26d03c454940",
        "store_id": null,
        "transaction_ref": "98d90ca6-b842-4596-af08-bce3c47c9857",
        "pin_code": "3093-0622-2855-3097",
        "pin_mask": "3093-****-****-3097",
        "amount": 5000,
        "balance": 5000,
        "currency": "NGN",
        "mode": "test",
        "customer": {
            "first_name": "Ada",
            "last_name": "Okafor",
            "email": "[email protected]",
            "phone": "+2348031234567"
        },
        "status": "active",
        "external_reference": "order-7f3a91",
        "expires_at": "2027-10-27T09:14:22.000000Z",
        "sold_at": "2026-10-27T09:14:22.000000Z",
        "keep_code": false,
        "daily_limit": null,
        "keep_code_notice": null,
        "handle_delivery": false,
        "google_wallet_url": "https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3Mi...",
        "apple_wallet_url": "https://afropin.com/api/wallet/apple/add/0fbdf173-db9d-4efa-837b-42e621e8af98/SFkz3ZWoF6Ph0d0cZi_JVv3fyl3UU5hkvbPrvqxwcsw"
    }
}

Errors

Failed requests return a stable code with a translated message. Branch on code, never on the message.

422 validation_error Some fields are missing or invalid
403 wrong_account_type Your account type cannot use this endpoint
403 issuing_disabled Issuing PINs is not switched on for your account. Contact support
429 rate_limited Too many requests. Wait a moment and try again
422 duplicate_external_reference You have sent this external_reference before
422 invalid_webhook_id No webhook of yours has this webhook_id
422 invalid_store_id No store of yours has this store_id
404 catalog_item_not_found No item in your catalog has this catalog_id
403 pin_type_inactive This PIN type is paused
422 amount_not_offered This product is only sold in set amounts, and this is not one of them
422 amount_too_small The amount is below this product's minimum
422 amount_too_large The amount is above this product's maximum
422 keep_code_not_allowed This product does not let customers keep the same code (allow_keep_code in the catalog)
422 daily_limit_not_allowed This product is spent in one go, so a PIN of it cannot have a daily limit
403 currency_not_supported Your account has no wallet in this currency
402 wallet_balance_low Your wallet balance does not cover this amount
500 pin_generation_failed A new PIN code could not be created. Try again
500 internal_error Something went wrong on our side. Try again; if it keeps happening, contact support
{
    "ok": false,
    "error": {
        "code": "catalog_item_not_found",
        "message": "No item in your catalog has this catalog_id",
        "fields": null
    }
}
All error codes →