jadePOS API Reference
ทำให้ร้านทำงานอัตโนมัติ: สร้างรายการเก็บเงิน จัดการสินค้า อ่านบัญชี ยืนยันตัวตนด้วยกุญแจ API จากบัญชีของคุณ
Conventions
| Base URL | https://jadepos.store |
| Authentication | Authorization: Bearer jade_… — create and revoke keys under Account → API keys. Keys are stored hashed; the value is shown once. |
| Request bodies | application/x-www-form-urlencoded form fields (as in the curl examples) |
| Responses | JSON, application/json; charset=utf-8 |
| Amounts | Integers in minor units: 1250 = $12.50. JPY/KRW have no minor unit — pass whole yen/won. |
| Rate limits | Reads 60/min, product writes 60/min, charges 30/min (per IP). Exceeding answers 429 {"error":"rate_limited"}. |
| Errors | Non-2xx responses carry {"error":"human readable reason"} |
GET/api/v1/ping
Liveness check. The only endpoint that needs no key.
curl https://jadepos.store/api/v1/ping
→ {"ok":true,"service":"jadepos.store"}GET/api/v1/me
The merchant who owns the key — use it to verify a key and read your ledger currency.
curl -H "Authorization: Bearer jade_..." https://jadepos.store/api/v1/me
→ {"username":"demo","business_name":"Jade Cafe","currency":"USD"}GET/api/v1/products
Your full catalog, active and archived.
curl -H "Authorization: Bearer jade_..." https://jadepos.store/api/v1/products
→ {"products":[{"sku":"latte","name":"Latte","description":"Double shot",
"kind":"product","price_cents":450,"currency":"USD","tax_percent":8.5,
"requires_shipping":false,"active":true}]}POST/api/v1/products
Create a catalog item, or update it if the SKU already exists (upsert).
| Field | Description | |
|---|---|---|
| sku | required | Letters, digits, dot, dash, underscore; max 40. The upsert key. |
| name | required | Display name, max 120 chars. |
| price_cents | required | Integer minor units. |
| description | optional | Up to 500 chars. |
| kind | optional | product (default) or service. |
| currency | optional | Defaults to your ledger currency. |
| tax_percent | optional | Per-line tax rate, e.g. 8.5. Default 0. |
| requires_shipping | optional | 1 to demand a shipping address at checkout. Default 0. |
curl -X POST https://jadepos.store/api/v1/products \
-H "Authorization: Bearer jade_..." \
-d sku=beans-1kg -d "name=Coffee Beans 1kg" -d price_cents=1899 -d requires_shipping=1
→ {"ok":true,"sku":"beans-1kg","created":true}GET/api/v1/orders
Your orders, newest first, 50 per page. Draft carts are never included.
| Field | Description | |
|---|---|---|
| status | optional | paid, pending, declined, refunded, void; empty = all. |
| page | optional | 1-based page number. |
curl -H "Authorization: Bearer jade_..." "https://jadepos.store/api/v1/orders?status=paid&page=1"
→ {"orders":[{"ref":"JP-2524f49c22","status":"paid","total_cents":800,
"currency":"USD","customer_name":"Lin","created_at":"2026-08-27 09:12:44",
"paid_at":"2026-08-27 09:12:45"}]}GET/api/v1/transactions
The money ledger: one row per charge or refund attempt, newest first, 50 per page (page param). Card rows carry only the brand and last four digits — never the full number.
curl -H "Authorization: Bearer jade_..." https://jadepos.store/api/v1/transactions
→ {"transactions":[{"tx_ref":"TX-63505a62be6f","order_ref":"JP-e4a6c4dd8d",
"kind":"charge","method":"card","mode":"test","status":"approved",
"amount_cents":1250,"currency":"USD","card_brand":"amex",
"card_last4":"0005","auth_code":"F94FAC","created_at":"2026-08-26 16:04:46"}]}POST/api/v1/charges
Validate and charge a card, or record a PayPal / Cash App payment you already received. Creates a one-line order, charges it, and returns the outcome with a public receipt URL.
Common fields
| Field | Description | |
|---|---|---|
| method | required | card, paypal, or cashapp. Wallets must be enabled in Payments. |
| amount_cents | required | Positive integer minor units. |
| currency | optional | Defaults to your ledger currency. |
| description | optional | Line-item text on the order and receipt. |
| customer_name | optional | Shown on the order. |
| customer_email | optional | The receipt link is emailed when the mail watcher is configured. |
| ship_name … ship_country | optional | Shipping address: ship_name, ship_address1, ship_address2, ship_city, ship_region, ship_postal, ship_country. When ship_address1 is present the order gets a printable label at /label?ref=… (merchant login). |
Card fields (method=card)
| Field | Description | |
|---|---|---|
| card_number | required | Digits, spaces and dashes allowed. Never stored. |
| exp_month | required | 1–12. |
| exp_year | required | 4-digit (2030) or 2-digit (30). |
| cvv | required | 3 digits; 4 for American Express. Never stored. |
Wallet fields (method=paypal | cashapp)
| Field | Description | |
|---|---|---|
| reference | optional | Your note of the wallet payment (transaction id, screenshot ref). |
curl -X POST https://jadepos.store/api/v1/charges \
-H "Authorization: Bearer jade_..." \
-d method=card -d amount_cents=1250 -d currency=USD \
-d "description=Latte x2" \
-d card_number=4242424242424242 -d exp_month=12 -d exp_year=2030 -d cvv=123
→ 200 {"approved":true,"order_ref":"JP-1a2b3c4d5e","auth_code":"A1B2C3",
"card_brand":"visa","card_last4":"4242","amount_cents":1250,
"currency":"USD","receipt_url":"https://jadepos.store/receipt?ref=JP-1a2b3c4d5e"}
→ 402 {"approved":false,"order_ref":"JP-8f54674005","card_brand":"visa",
"error":"Card declined (test card)"}Whether a card charge is simulated (test) or sent to your processor (live) follows the card method's mode in Payments. The receipt URL is public but unguessable; it shows no card data beyond brand + last4.
Customers
The customer book subscriptions bill against.
GET/api/v1/customers
Search/list your customers, newest first, 50 per page.
| Field | Description | |
|---|---|---|
| q | optional | Search name or email (substring match). |
| page | optional | 1-based page number. |
curl -H "Authorization: Bearer jade_..." "https://jadepos.store/api/v1/customers?q=lin"
→ {"customers":[{"id":3,"name":"Lin","email":"lin@example.com",
"phone":"","status":"active","created_at":"2026-08-27 09:12:44"}]}POST/api/v1/customers
Create a customer, or update one if id is given (upsert).
| Field | Description | |
|---|---|---|
| id | optional | 0 or absent creates a new customer; a positive id updates that one. |
| name | required | Max 120 chars. |
| email, phone, address1, address2, city, region, postal, country, notes | optional |
curl -X POST https://jadepos.store/api/v1/customers \
-H "Authorization: Bearer jade_..." \
-d "name=Lin" -d email=lin@example.com
→ {"ok":true,"id":3,"created":true}GET/api/v1/customers/get
One customer, by id or email.
| Field | Description | |
|---|---|---|
| id or email | required | One of the two. |
curl -H "Authorization: Bearer jade_..." "https://jadepos.store/api/v1/customers/get?email=lin@example.com"
Plans
Pricing blueprints subscriptions are created against. Editing a plan never changes what an existing subscriber owes — price and interval are snapshotted onto the subscription at signup.
GET/api/v1/plans
All plans, active and archived.
curl -H "Authorization: Bearer jade_..." https://jadepos.store/api/v1/plans
→ {"plans":[{"code":"pro-monthly","name":"Pro","description":"",
"price_cents":1999,"currency":"USD","interval_unit":"month",
"interval_count":1,"trial_days":14,"active":true}]}POST/api/v1/plans
Create a plan, or update one if code already exists (upsert).
| Field | Description | |
|---|---|---|
| code | required | Letters, digits, dot, dash, underscore; max 40. The upsert key. |
| name | required | Display name. |
| price_cents | required | Integer minor units, charged each cycle. |
| description | optional | |
| currency | optional | Defaults to your ledger currency. |
| interval_unit | optional | day, week, month (default), or year. |
| interval_count | optional | Bill every N units. Default 1. |
| trial_days | optional | Free trial before the first charge. Default 0. |
curl -X POST https://jadepos.store/api/v1/plans \
-H "Authorization: Bearer jade_..." \
-d code=pro-monthly -d name=Pro -d price_cents=1999 -d trial_days=14
→ {"ok":true,"code":"pro-monthly","created":true}Subscriptions
Recurring billing engine. Card subscriptions auto-charge (test mode simulates; live mode tokenizes the card through your gateway relay and charges the token each cycle — the PAN is never stored). PayPal/Cash App subscriptions create a pending invoice each cycle that you confirm with /confirm-payment once you've actually been paid, same as a one-off wallet checkout.
GET/api/v1/subscriptions
Your subscriptions, newest first, 50 per page.
| Field | Description | |
|---|---|---|
| status | optional | trialing, active, past_due, paused, canceled; empty = all. |
| page | optional | 1-based page number. |
curl -H "Authorization: Bearer jade_..." "https://jadepos.store/api/v1/subscriptions?status=active"
GET/api/v1/subscriptions/get
One subscription plus its full invoice history.
| Field | Description | |
|---|---|---|
| ref | required | The SUB-... reference. |
curl -H "Authorization: Bearer jade_..." "https://jadepos.store/api/v1/subscriptions/get?ref=SUB-1a2b3c4d5e"
→ {"ref":"SUB-1a2b3c4d5e","status":"active","method":"card",
"plan_name":"Pro","price_cents":1999,"currency":"USD",
"current_period_end":"2026-09-27 09:12:44","next_billing_at":"2026-09-27 09:12:44",
"cancel_at_period_end":false,"failed_attempts":0,
"invoices":[{"ref":"INV-63505a","period_start":"...","period_end":"...",
"amount_cents":1999,"status":"paid","attempt_count":1,"paid_at":"..."}]}POST/api/v1/subscriptions
Start a subscription. Non-trial plans are charged immediately — the response reflects that first attempt.
| Field | Description | |
|---|---|---|
| plan_code | required | |
| customer_id | optional | An existing customer id. |
| customer_name, customer_email | optional | Used instead of customer_id — finds an existing customer by email or creates one. |
| method | optional | card (default), paypal, or cashapp — must be enabled in Payments. |
| payment_method_id | optional | Reuse a card already on file for this customer instead of the fields below. |
| card_number, exp_month, exp_year, cvv | optional | Same rules as /charges. Never stored — only brand/last4 (and a gateway token in live mode). |
curl -X POST https://jadepos.store/api/v1/subscriptions \
-H "Authorization: Bearer jade_..." \
-d plan_code=pro-monthly -d customer_email=lin@example.com -d "customer_name=Lin" \
-d card_number=4242424242424242 -d exp_month=12 -d exp_year=2030 -d cvv=123
→ {"ok":true,"ref":"SUB-1a2b3c4d5e","status":"active",
"current_period_end":"2026-09-27 09:12:44","next_billing_at":"2026-09-27 09:12:44"}POST/api/v1/subscriptions/cancel
Cancel a subscription, immediately or at the end of the current period.
| Field | Description | |
|---|---|---|
| ref | required | |
| at_period_end | optional | 1 to keep it active through the current period, then auto-cancel with no further charge. Default 0 (cancels now). |
| reason | optional |
curl -X POST https://jadepos.store/api/v1/subscriptions/cancel \ -H "Authorization: Bearer jade_..." -d ref=SUB-1a2b3c4d5e -d at_period_end=1
POST/api/v1/subscriptions/renew
past_due: retries the outstanding invoice. Otherwise: closes the current period now and charges the new one (early renewal).
| Field | Description | |
|---|---|---|
| ref | required |
curl -X POST https://jadepos.store/api/v1/subscriptions/renew -H "Authorization: Bearer jade_..." -d ref=SUB-1a2b3c4d5e
→ {"ok":true,"status":"active","current_period_end":"2026-10-27 09:12:44"}POST/api/v1/subscriptions/pause & /resume
Pause stops billing until resumed; resume puts it back in the billing queue.
| Field | Description | |
|---|---|---|
| ref | required |
curl -X POST https://jadepos.store/api/v1/subscriptions/pause -H "Authorization: Bearer jade_..." -d ref=SUB-1a2b3c4d5e
POST/api/v1/subscriptions/confirm-payment
Confirm the oldest outstanding invoice on a PayPal/Cash App subscription — the API equivalent of the portal's "Mark paid".
| Field | Description | |
|---|---|---|
| ref | required |
curl -X POST https://jadepos.store/api/v1/subscriptions/confirm-payment -H "Authorization: Bearer jade_..." -d ref=SUB-1a2b3c4d5e
→ {"ok":true,"invoice_ref":"INV-63505a"}GET/api/v1/invoices
The subscription billing ledger: one row per billing period across every subscription.
| Field | Description | |
|---|---|---|
| status | optional | outstanding (default: pending + failed), paid, void, or empty for all. |
| page | optional | 1-based page number. |
curl -H "Authorization: Bearer jade_..." "https://jadepos.store/api/v1/invoices?status=outstanding"
→ {"invoices":[{"ref":"INV-63505a","subscription_ref":"SUB-1a2b3c4d5e",
"method":"card","customer_name":"Lin","amount_cents":1999,"currency":"USD",
"status":"failed","attempt_count":1,"due_at":"2026-09-27 09:12:44",
"created_at":"2026-09-27 09:12:44"}]}Card validation
Every card is validated before any charge is attempted. A failure answers 402 with a reason and nothing is sent to the processor.
| Network | Detected by | Length | CVV |
|---|---|---|---|
| VISA Visa | 4… | 13, 16, 19 | 3 |
| Mastercard Mastercard | 51–55, 2221–2720 | 16 | 3 |
| DISCOVER Discover | 6011, 644–649, 65 | 16–19 | 3 |
| AMEX American Express | 34, 37 | 15 | 4 |
| JCB JCB | 3528–3589 | 16–19 | 3 |
| DINERS Diners Club | 300–305, 36, 38–39 | 14–19 | 3 |
Plus the Luhn checksum and an expiry check against the current date. Other networks are rejected. (Diners and JCB settle on the Discover network in the US.)
Test cards (test mode)
Any future expiry and matching-length CVV work with these:
| VISA | 4242 4242 4242 4242 | approved |
| MC | 5555 5555 5555 4444 | approved |
| DISC | 6011 1111 1111 1117 | approved |
| AMEX | 3782 822463 10005 | approved |
| VISA | 4000 0000 0000 0002 | declined |
Live card gateway relay
In live mode jadePOS POSTs each card charge as JSON to the HTTPS relay URL configured in Payments (loopback http://127.0.0.1 is also accepted for a relay on the same machine), with your relay key in the body:
{"api_key":"...","reference":"pre-...","amount_cents":1250,"currency":"USD",
"card_number":"...","exp_month":"12","exp_year":"2030","cvv":"..."}The relay must answer within ~20 seconds:
{"status":"approved","auth_code":"..."}
{"status":"declined","error":"..."}A ready-to-run relay for NMI and Authorize.net ships with the project: deploy/card_relay.py (Python stdlib, no installs) with deploy/relay_config.json choosing the processor and holding its credentials — they never enter jadePOS. It ships pointed at NMI's public sandbox until you swap in real keys; for Authorize.net, create free sandbox credentials at developer.authorize.net and set sandbox: true until go-live.
The same endpoint also handles recurring billing's two extra request shapes (put a card on file, then charge it later without ever touching the PAN again):
{"api_key":"...","tokenize":true,"card_number":"...","exp_month":"12","exp_year":"2030","cvv":"...",...}
→ {"status":"tokenized","token":"..."} (NMI Customer Vault / Authorize.Net CIM)
{"api_key":"...","charge_token":true,"reference":"...","amount_cents":1999,"currency":"USD","token":"..."}
→ {"status":"approved","auth_code":"..."} / {"status":"declined","error":"..."}Processor webhooks
Point your processor's webhook (Authorize.Net: net.authorize.payment.refund.created, …void.created, …fraud.declined) at https://jadepos.store/webhook/authnet. The relay verifies the X-ANET-Signature HMAC-SHA512 against your Signature Key (Ling has no HMAC builtin, so it must happen in the relay), then forwards the verified event to the till over loopback.
The benefit: money that moves outside the till stays truthful inside it — a refund or void issued from the Authorize.Net dashboard automatically flips the matching order (correlated by the order ref jadePOS sends as the transaction reference), records it on the ledger, and updates the customer's receipt. Delivery is idempotent, so retried webhooks never double-refund.
Status codes
200 | Success — including recorded declines on wallet endpoints; card declines use 402. |
400 | Malformed request: missing/invalid field, unknown method, bad SKU. |
401 | Missing, revoked, or wrong API key. |
402 | Card charge declined (validation failure or processor decline) — body carries approved:false and error. |
404 | Unknown route. |
429 | Rate limited — slow down and retry. |