Skip to content

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

diagram

5. Common Errors

SymptomCause
401 invalid_credentialsBad signature / expired timestamp / nonce replay / unknown AppID
400 openapi_idempotency_key_requiredWrite endpoint without Idempotency-Key header
409 openapi_idempotency_key_conflictSame key, different body
429Rate limit hit (600 req/min)

See the error code dictionary.

Next Steps

Released under MIT-equivalent terms.