Quick Start
Listing virtual card headers is the fastest way to verify your auth chain (no side effects, no idempotency key needed).
1. Get Credentials
Generate a credential pair from the user dashboard:
APP_ID:cp_+ 28 hex characters (31 total)SECRET: 64 hex characters
Store securely
SECRET is only returned once at generation. If lost you must reset. Save it to KMS / Vault / GCP Secret Manager.
2. Copy a Snippet
bash
APP_ID="cp_a1b2c3d4..."
SECRET="f1e2d3c4..."
BASE="https://api.coinepay.net"
PATH_VAL="/api/v1/openapi/card_headers/list"
BODY='{"page":1,"page_size":20}'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
SIGN_INPUT=$(printf 'POST\n%s\n\n%s\n%s\n%s' "$PATH_VAL" "$TS" "$NONCE" "$BODY_HASH")
SIG=$(echo -n "$SIGN_INPUT" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
curl -X POST "$BASE$PATH_VAL" \
-H "X-App-Id: $APP_ID" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIG" \
-H "Content-Type: application/json" \
-d "$BODY"python
import time, hmac, hashlib, secrets, json, requests
APP_ID = "cp_a1b2c3d4..."
SECRET = "f1e2d3c4..."
BASE = "https://api.coinepay.net"
PATH = "/api/v1/openapi/card_headers/list"
BODY = json.dumps({"page": 1, "page_size": 20}, separators=(",", ":")).encode()
ts = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(BODY).hexdigest()
sign_input = f"POST\n{PATH}\n\n{ts}\n{nonce}\n{body_hash}"
sig = hmac.new(SECRET.encode(), sign_input.encode(), hashlib.sha256).hexdigest()
r = requests.post(BASE + PATH, headers={
"X-App-Id": APP_ID, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sig,
"Content-Type": "application/json",
}, data=BODY)
print(r.json())js
import crypto from 'node:crypto'
const APP_ID = 'cp_a1b2c3d4...'
const SECRET = 'f1e2d3c4...'
const BASE = 'https://api.coinepay.net'
const PATH = '/api/v1/openapi/card_headers/list'
const body = JSON.stringify({ page: 1, page_size: 20 })
const ts = Math.floor(Date.now() / 1000).toString()
const nonce = crypto.randomBytes(16).toString('hex')
const bodyHash = crypto.createHash('sha256').update(body).digest('hex')
const signInput = `POST\n${PATH}\n\n${ts}\n${nonce}\n${bodyHash}`
const sig = crypto.createHmac('sha256', SECRET).update(signInput).digest('hex')
const res = await fetch(BASE + PATH, {
method: 'POST',
headers: {
'X-App-Id': APP_ID, 'X-Timestamp': ts, 'X-Nonce': nonce, 'X-Signature': sig,
'Content-Type': 'application/json',
},
body,
})
console.log(await res.json())3. Expected Response
json
{
"code": 200,
"message": "OK",
"data": {
"list": [
{
"header_id": "hdr_5",
"card_bin": "424242",
"card_brand": "VISA",
"card_area": "United States",
"business_scene": "Cross-border spending",
"description": { "zh-CN": "适合电商订阅", "en-US": "For e-commerce subscriptions" },
"features": ["3DS", "EMV"],
"require_phone": false,
"require_email": true
}
],
"total": 12,
"page": 1,
"page_size": 20,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
}4. Full Card-Issuance Flow
5. Common Errors
| Symptom | Cause |
|---|---|
| 401 invalid_credentials | Bad signature / expired timestamp / nonce replay / unknown AppID |
| 400 openapi_idempotency_key_required | Write endpoint without Idempotency-Key header |
| 409 openapi_idempotency_key_conflict | Same key, different body |
| 429 | Rate limit hit (600 req/min) |
See the error code dictionary.
Next Steps
- HMAC Authentication — read the signing spec end-to-end to avoid mysterious 401s
- Webhook Spec — receive async events like card-opened
- Code Samples — full Python / Go / Java / PHP clients