Receiptify
API v2.0.0 ยท 32+ endpoints
Getting Started
๐Ÿš€ Quickstart
๐Ÿ”‘ Auth
๐Ÿ“ฆ Response format
โš  Error examples
Status
GET/v1/ping
GET/v1/health
GET/v1/changelog
Info
GET/v1/brands
GET/v1/brands/search
GET/v1/brands/{slug}
GET/v1/templates
Account
GET/v1/me
PATCH/v1/me
GET/v1/usage
GET/v1/analytics
Receipt
POST/v1/validate
POST/v1/preview
POST/v1/generate
POST/v1/generate/batch
GET/v1/receipts
GET/v1/receipts/{id}
GET/v1/receipts/{id}/html
POST/v1/receipts/{id}/resend
DELETE/v1/receipts/{id}
Webhooks
POST/v1/webhooks
GET/v1/webhooks
POST/v1/webhooks/{id}/test
DELETE/v1/webhooks/{id}
Admin
POSTkeys/create
POSTkeys/bulk-create
PATCHkeys/{key}
POSTkeys/revoke
POSTkeys/reactivate
POSTkeys/reset-quota
GETkeys (list)
GETkeys/{key}
POSTkeys/{key}/note
GETkeys/{key}/receipts
GETadmin/search
GETadmin/owner/{id}
GETadmin/receipts
GETadmin/analytics
GETadmin/stats
POSTadmin/db/cleanup
Integration Templates
๐Ÿ“ฆ discord.py ยท discord.js ยท Telegram ยท Web ยท PHP
Reference
โš  Error codes
๐Ÿ” Rate limits
๐Ÿ”’ Idempotency
๐Ÿ”” Webhook events

Receiptify API Docs

v2.0.0 live

Generate 1:1 brand receipts for 52 stores. Webhooks, batch sends, receipt history, analytics, and copy-paste bot templates for Discord, Telegram, and web.

Base: https://api.reciptifydev.xyz Version 2.0.0 Auth: Bearer token 52 brands 49 endpoints
๐Ÿš€ Start Here โ€” 3-step quickstart
Every integration should follow this flow. validate โ†’ preview โ†’ generate. The first two steps are free and don't consume quota.
STEP 1 ยท FREE
POST /v1/validate
Check brand slug, URL, price, currency. Returns a human-ready discord_feedback string on any issue โ€” no parsing needed.
STEP 2 ยท FREE
POST /v1/preview
Render the full receipt HTML to base64. Inspect before sending. No email sent.
STEP 3 ยท USES QUOTA
POST /v1/generate
Send the receipt to an email. Set Idempotency-Key to safely retry.
Python โ€” complete quickstart
import requests

KEY  = "rcptfy_live_your_key_here"
BASE = "https://api.reciptifydev.xyz/v1"
AUTH = {"Authorization": f"Bearer {KEY}"}

# โ”€โ”€ Step 1: validate (free) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
vr = requests.post(f"{BASE}/validate", json={
    "brand": "nike",
    "product_url": "https://www.nike.com/t/air-force-1-07/CW2288-111",
    "price": "110.00",
    "currency": "ยฃ"
}, headers=AUTH).json()

if not vr["data"]["valid"]:
    print(vr["data"]["discord_feedback"])  # ready-to-send error message
    exit()

slug = vr["data"]["brand_slug"]  # "nike"

# โ”€โ”€ Step 2: preview (free) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import base64
pr = requests.post(f"{BASE}/preview", json={
    "brand": slug, "product_url": "https://www.nike.com/t/air-force-1-07/CW2288-111",
    "price": "110.00", "currency": "ยฃ"
}, headers=AUTH).json()

html = base64.b64decode(pr["data"]["html_base64"]).decode()
with open("preview.html", "w") as f: f.write(html)
print(f"Preview saved โ€” {pr['data']['html_length']} bytes, order #{pr['data']['order_number']}")

# โ”€โ”€ Step 3: generate (uses 1 quota request) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
gr = requests.post(f"{BASE}/generate", json={
    "brand": slug,
    "product_url": "https://www.nike.com/t/air-force-1-07/CW2288-111",
    "price": "110.00",
    "currency": "ยฃ",
    "email_mode": "normal",
    "size": "UK 9",
    "recipient_email": "buyer@gmail.com",
    "external_id": "order_9281"
}, headers={**AUTH, "Idempotency-Key": "order-nike-110-9281"}).json()

if gr["success"]:
    d = gr["data"]
    print(f"โœ… Sent! Order #{d['order_number']} ยท {d['product_name']} โ†’ {d['sent_to']}")
    print(f"   Receipt ID: {d['receipt_id']}  (use this to resend or look up later)")
else:
    e = gr["error"]
    print(f"โŒ [{e['code']}] {e['message']}")
Authentication
Send your API key as a Bearer token in every authenticated request:
Authorization: Bearer rcptfy_live_abcdefghijklmnop12345678

Key types:
rcptfy_live_ โ€” real key, emails are sent
rcptfy_test_ โ€” test key, receipts are generated but no emails are ever sent

Quota-free endpoints (no request consumed):
GET /v1/ping ยท GET /v1/health ยท GET /v1/brands* ยท GET /v1/templates ยท GET /v1/changelog ยท POST /v1/validate ยท POST /v1/preview ยท GET /v1/usage ยท GET /v1/me ยท GET /v1/analytics ยท GET /v1/receipts*
Response Format
Every response follows the same envelope. No exceptions.
Success envelope
{
  "success": true,
  "request_id": "req_a1b2c3d4e5f6g7h8",   // unique per request โ€” include in bug reports
  "data": { ... }                            // endpoint-specific payload
}
Error envelope
{
  "success": false,
  "request_id": "req_a1b2c3d4e5f6g7h8",
  "error": {
    "code": "invalid_brand",               // stable machine-readable string โ€” never changes
    "message": "Brand slug not recognised.",
    "discord_feedback": "โŒ **Error `invalid_brand`**
Brand slug not recognised.
๐Ÿ’ก Call GET /v1/brands for the full list",
    "details": { ... }                     // optional โ€” per-field errors on validation failures
  }
}
Common Error Examples
ERR
401 โ€” invalid_api_key
Wrong key, typo, or missing Authorization header
โ–ถ
Response
HTTP/1.1 401 Unauthorized
X-Request-Id: req_a1b2c3d4e5f6g7h8

{
  "success": false,
  "request_id": "req_a1b2c3d4e5f6g7h8",
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key. Keys start with rcptfy_live_ or rcptfy_test_.",
    "discord_feedback": "โŒ **Error `invalid_api_key`**
Invalid API key.
๐Ÿ’ก Get a key by joining our Discord and purchasing a plan.",
    "details": {"hint": "Get a key by joining our Discord and purchasing a plan."}
  }
}
๐Ÿ’ก Check the key starts with rcptfy_live_ or rcptfy_test_ and that you're not including extra whitespace.
ERR
429 โ€” rate_limit_exceeded
Quota exhausted for this key
โ–ถ
Response
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1748822400
X-Request-Id: req_a1b2c3d4e5f6g7h8

{
  "success": false,
  "request_id": "req_a1b2c3d4e5f6g7h8",
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Quota exhausted โ€” 200 of 200 requests used.",
    "discord_feedback": "โŒ **Error `rate_limit_exceeded`**
Quota exhausted.
๐Ÿ’ก Contact support on Discord to upgrade or reset your quota.",
    "details": {"requests_used": 200, "requests_limit": 200, "remaining": 0,
                "hint": "Contact support on Discord to upgrade or reset your quota."}
  }
}
๐Ÿ’ก Resets at midnight UTC. Check X-RateLimit-Reset header for exact timestamp. Use GET /v1/usage to check remaining quota before making requests.
ERR
400 โ€” invalid_brand
Brand slug not recognised
โ–ถ
Response
HTTP/1.1 400 Bad Request

{
  "success": false,
  "request_id": "req_a1b2c3d4e5f6g7h8",
  "error": {
    "code": "invalid_brand",
    "message": ""nikee" is not a supported brand slug.",
    "discord_feedback": "โŒ **Error `invalid_brand`**
"nikee" is not a supported brand slug.
๐Ÿ’ก See GET /v1/brands for the full list",
    "details": {
      "brand": "nikee",
      "valid_examples": "acnestudios, adidas, amazon, arcteryx, apple...",
      "hint": "Call GET /v1/brands for the full list"
    }
  }
}
๐Ÿ’ก Use GET /v1/brands/search?q=nik to find valid slugs by partial name match.
ERR
422 โ€” validation_error
One or more body fields failed validation
โ–ถ
Response
HTTP/1.1 422 Unprocessable Entity

{
  "success": false,
  "request_id": "req_a1b2c3d4e5f6g7h8",
  "error": {
    "code": "validation_error",
    "message": "Request body failed field-level validation.",
    "discord_feedback": "โŒ **Error `validation_error`**
Request body failed field-level validation.
โ€ข **currency**: must be one of: $ ยฃ โ‚ฌ ยฅ
โ€ข **price**: Decimal string required e.g. "110.00"",
    "details": {
      "currency": "must be one of: $ ยฃ โ‚ฌ ยฅ",
      "price": "Decimal string required e.g. "110.00" โ€” got "ยฃ110""
    }
  }
}
๐Ÿ’ก Check error.details for per-field errors. The discord_feedback string is pre-formatted to send directly to a Discord user.
ERR
500 โ€” email_send_failed
Transient email delivery error โ€” retry once
โ–ถ
Response
HTTP/1.1 500 Internal Server Error

{
  "success": false,
  "request_id": "req_a1b2c3d4e5f6g7h8",
  "error": {
    "code": "email_send_failed",
    "message": "Email delivery failed. Retry once, then contact support.",
    "discord_feedback": "โŒ **Error `email_send_failed`**
Email delivery failed.
๐Ÿ’ก Include request_id when contacting support.",
    "details": {"hint": "Include request_id when contacting support."}
  }
}
๐Ÿ’ก Retry with the same Idempotency-Key โ€” if the first attempt actually sent, you'll get the cached success response. If it retries and fails again, contact support with the request_id.
Status
GET
/v1/ping
No-auth heartbeat for uptime monitors.
No authโ–ถ
Response
{"pong": true, "ts": "2025-06-01T12:00:00Z"}
โ„น /v1/ping is the only endpoint that returns a bare object (no envelope). All other endpoints use the standard envelope.
GET
/v1/health
Full status โ€” version, uptime, brand count, receipt totals, keys active.
No authโ–ถ
Response
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{"status":"online","version":"2.0.0","uptime":"4h 22m 11s",
  "server_time":"2025-06-01T12:00:00Z","brands":52,"active_keys":41,
  "receipts_sent":9823,"receipts_today":142}}
GET
/v1/changelog
API version history and change log.
No authโ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/changelog
Info
GET
/v1/brands
All 52 brands with slugs, names, regions, and active status. Filter by ?region=US|DE|AUTH.
No authโ–ถ
โš  Use the slug (e.g. nike) in POST /v1/generate โ€” not the display name.
cURL
curl https://api.reciptifydev.xyz/v1/brands?region=US
Response shape
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{"total":52,"brands":[
  {"slug":"nike","name":"Nike","region":"US","active":true}, ...
]}}
GET
/v1/brands/search?q=nik
Fuzzy search by name or slug. Returns [] on no match โ€” never a 404.
No authโ–ถ
Examples
curl "https://api.reciptifydev.xyz/v1/brands/search?q=louis"
curl "https://api.reciptifydev.xyz/v1/brands/search?q=nik®ion=US"
GET
/v1/brands/{slug}
Single brand detail + template_available flag. Returns fuzzy suggestions on 404.
No authโ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/brands/nike
GET
/v1/templates
Confirmed-working brands only โ€” template file verified present. Use as your bot's allowed-list.
No authโ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/templates
Account
GET
/v1/me
Full account: label, plan, detailed quota with % used and ASCII bar, name/email/address.
AuthFree
โ–ถ
Response
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{"label":"John 1mo","plan":"standard","expires_at":"2025-07-01 00:00:00",
  "quota":{"used":47,"limit":500,"remaining":453,"percent_used":9.4,"bar":"[#........] 9.4%"},
  "account":{"name":"John Smith","email":"john@gmail.com","country":"UK"}}}
PATCH
/v1/me
Update your account name, email, or address. Changes apply to all future receipts immediately.
AuthFree
โ–ถ
Body (all optional)
FieldExample
nameJohn Smith
emailjohn@gmail.com
street123 High Street
cityLondon
zippSW1A 1AA
countryUnited Kingdom
cURL
curl -X PATCH https://api.reciptifydev.xyz/v1/me   -H "Authorization: Bearer rcptfy_live_..."   -H "Content-Type: application/json"   -d '{"name":"John Smith","email":"john@gmail.com","city":"London"}'
GET
/v1/usage
Quota only. Returns discord_summary โ€” paste straight into an embed field.
AuthFree
โ–ถ
Response
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{"used":47,"limit":500,"remaining":453,"percent_used":9.4,
  "resets_at":1748822400,"bar":"[#........] 9.4%",
  "discord_summary":"**453** requests left out of **500** (91% remaining)"}}
GET
/v1/analytics?days=30
Per-day receipt counts, top brands, and email mode split. Query range: 1โ€“90 days.
AuthFree
โ–ถ
Response
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{"period_days":30,"total_sent":142,"total_dry_run":5,
  "daily":[{"date":"2025-05-01","count":8},{"date":"2025-05-02","count":12}],
  "peak_day":"2025-05-14",
  "top_brands":[{"slug":"nike","count":41},{"slug":"louisvuitton","count":28}],
  "email_modes":{"normal":130,"spoofed":12}}.],
  "email_modes":{"normal":130,"spoofed":12}}}
Receipt
POST
/v1/validate
Pre-flight โ€” check brand, URL, price, currency, email_mode. No email, no quota, free.
AuthFree
โ–ถ
Body
FieldDescription
brandREQBrand slug e.g. nike
product_urlREQFull https:// URL
priceOPTDecimal e.g. 110.00
currencyOPT$ ยฃ โ‚ฌ ยฅ
email_modeOPTnormal | spoofed
Success
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{"valid":true,"brand_slug":"nike",
  "brand_info":{"slug":"nike","name":"Nike","region":"US","active":true},
  "issues":{},"warnings":{},
  "discord_feedback":"โœ… All fields look good โ€” safe to call /generate"}}
Validation error
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{"valid":false,"brand_slug":null,
  "issues":{"brand":""nikee" is not a supported brand slug. Valid examples: acnestudios, adidas, amazon..."},
  "warnings":{},
  "discord_feedback":"โŒ Fix these before calling /generate:
โ€ข **brand**: "nikee" is not supported."}}
POST
/v1/preview
Render full receipt HTML to base64. No email, no quota. Same body as /generate.
AuthFree
โ–ถ
Python โ€” decode + view in browser
import base64, webbrowser, tempfile
resp = ... # POST /v1/preview response
html = base64.b64decode(resp["data"]["html_base64"]).decode()
with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False) as f:
    f.write(html); webbrowser.open(f.name)
POST
/v1/generate
Generate 1:1 receipt and email it. Auto-scrapes product name + image from URL.
Auth
โ–ถ
Body fields
FieldTypeDefaultDescription
brandREQstringโ€”Slug from GET /v1/brands, e.g. nike
product_urlREQstringโ€”Full product URL โ€” name + image auto-scraped
priceREQstringโ€”Decimal, no symbol, e.g. 110.00
currencyOPTstring$$ ยฃ โ‚ฌ ยฅ
email_modeOPTstringnormalnormal | spoofed
recipient_emailOPTstringaccount emailOverride send-to address
product_nameOPTstringauto-scrapedOverride name
product_imageOPTstringauto-scrapedOverride image URL
sizeOPTstringโ€”e.g. UK 9
colorOPTstringโ€”e.g. White/Black
arrivingOPTstringtodayDelivery date DD/MM/YYYY
order_dateOPTstringtodayOrder date DD/MM/YYYY
external_idOPTstringโ€”Your ref โ€” echoed in response, filterable in GET /v1/receipts
custom_subjectOPTstringbrand defaultOverride email subject
custom_senderOPTstringReceiptifyOverride sender name (normal mode)
dry_runOPTboolfalseRender the receipt and validate all fields without sending. Free โ€” no quota consumed.
Headers
HeaderDescription
AuthorizationBearer rcptfy_live_... or rcptfy_test_...
Idempotency-KeyUnique string โ€” only one email ever sent per key, safe to retry
Full cURL
curl -X POST https://api.reciptifydev.xyz/v1/generate   -H "Authorization: Bearer rcptfy_live_..."   -H "Idempotency-Key: order-nike-9281"   -H "Content-Type: application/json"   -d '{
    "brand": "nike",
    "product_url": "https://www.nike.com/t/air-force-1-07/CW2288-111",
    "price": "110.00", "currency": "ยฃ", "email_mode": "normal",
    "size": "UK 9", "color": "White",
    "recipient_email": "buyer@gmail.com",
    "external_id": "order_9281"
  }'
Success response
{"success":true,"request_id":"req_a1b2c3d4","data":{
  "receipt_id":"rcpt_9f2a1b3c","order_number":"847291038",
  "brand":"Nike","brand_slug":"nike","product_name":"Air Force 1 '07",
  "email_mode":"normal","sent_to":"buyer@gmail.com",
  "subject":"Order Received (Nike.com #847291038)",
  "created_at":"2025-06-01T12:00:00Z",
  "test_mode":false,"dry_run":false,"external_id":"order_9281"
}}
Response headers
HeaderValue
X-RateLimit-LimitTotal requests on your plan
X-RateLimit-RemainingRemaining requests
X-RateLimit-ResetUnix timestamp โ€” midnight UTC reset
X-Response-TimeProcessing time, e.g. 342.1ms
POST
/v1/generate/batch
Generate up to 5 receipts in one call. Items are processed independently โ€” one failure never blocks others. Each item counts as 1 request toward your quota.
Auth
โ–ถ
cURL
curl -X POST https://api.reciptifydev.xyz/v1/generate/batch   -H "Authorization: Bearer rcptfy_live_..." -H "Content-Type: application/json"   -d '{"items":[
    {"brand":"nike","product_url":"https://...","price":"110.00","currency":"ยฃ"},
    {"brand":"stockx","product_url":"https://...","price":"250.00","currency":"$"}
  ]}'
GET
/v1/receipts
Paginated history. Filter by brand, status, or external_id. Max 100/page.
AuthFree
โ–ถ
Query params
ParamDefaultDescription
brandโ€”Slug filter
statusโ€”sent | dry_run
external_idโ€”Your reference ID
limit20Max 100
offset0Pagination
GET
/v1/receipts/{receipt_id}
Full metadata for a single receipt by its receipt_id.
AuthFree
โ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/receipts/rcpt_9f2a1b3c -H "Authorization: Bearer rcptfy_live_..."
GET
/v1/receipts/{receipt_id}/html
Download rendered receipt HTML. No quota. Returns Content-Disposition: attachment.
AuthFree
โ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/receipts/rcpt_9f2a1b3c/html   -H "Authorization: Bearer rcptfy_live_..." -o receipt.html
POST
/v1/receipts/{receipt_id}/resend
Resend a past receipt. ?recipient_email= to override. Counts as 1 request.
Auth
โ–ถ
cURL
curl -X POST "https://api.reciptifydev.xyz/v1/receipts/rcpt_.../resend?recipient_email=other@gmail.com"   -H "Authorization: Bearer rcptfy_live_..."
DELETE
/v1/receipts/{receipt_id}
Remove a receipt from your history. The email already sent cannot be recalled โ€” this only deletes the record.
AuthFree
โ–ถ
cURL
curl -X DELETE https://api.reciptifydev.xyz/v1/receipts/rcpt_... -H "Authorization: Bearer rcptfy_live_..."
Webhooks
Events: receipt.sent ยท receipt.dry_run ยท quota.warning
POST to your HTTPS URL. Must respond 2xx within 5 seconds. After 5 consecutive failures, auto-deactivated.
Optionally provide a secret โ€” we'll sign the payload and send X-Receiptify-Signature: sha256=<hmac> so you can verify authenticity.
POST
/v1/webhooks
Register a webhook URL. Must be https://. Returns webhook_id.
AuthFree
โ–ถ
Body
FieldDescription
urlREQhttps:// endpoint to POST events to
eventsOPTComma-separated: receipt.sent,quota.warning
secretOPTSigning secret for X-Receiptify-Signature
cURL
curl -X POST https://api.reciptifydev.xyz/v1/webhooks   -H "Authorization: Bearer rcptfy_live_..." -H "Content-Type: application/json"   -d '{"url":"https://yourbot.com/hooks/rcptfy","events":"receipt.sent,quota.warning","secret":"s3cr3t"}'
Payload shapes
// receipt.sent
{"event":"receipt.sent","receipt_id":"rcpt_...","brand":"Nike","brand_slug":"nike",
 "order_number":"847291038","sent_to":"buyer@gmail.com","timestamp":"2025-06-01T12:00:00Z"}

// quota.warning (fires at 80%, 90%, 100% of quota)
{"event":"quota.warning","threshold_pct":80,"used":400,"limit":500,"remaining":100}
Python Flask โ€” verify signature
import hmac, hashlib
from flask import request, abort

SECRET = "s3cr3t"

@app.route("/hooks/rcptfy", methods=["POST"])
def webhook():
    sig = request.headers.get("X-Receiptify-Signature", "")
    expected = "sha256=" + hmac.new(SECRET.encode(), request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        abort(403)
    evt = request.json
    if evt["event"] == "receipt.sent":
        print(f"โœ… {evt['brand']} receipt โ€” order #{evt['order_number']} sent to {evt['sent_to']}")
    elif evt["event"] == "quota.warning":
        print(f"โš ๏ธ Quota at {evt['threshold_pct']}% โ€” {evt['remaining']} requests left")
    return "OK", 200
GET
/v1/webhooks
List your webhooks. Secrets are never returned.
AuthFree
โ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/webhooks -H "Authorization: Bearer rcptfy_live_..."
POST
/v1/webhooks/{webhook_id}/test
Send a test receipt.sent payload to your registered URL.
AuthFree
โ–ถ
cURL
curl -X POST https://api.reciptifydev.xyz/v1/webhooks/wh_abc123/test -H "Authorization: Bearer rcptfy_live_..."
DELETE
/v1/webhooks/{webhook_id}
Remove a webhook.
AuthFree
โ–ถ
cURL
curl -X DELETE https://api.reciptifydev.xyz/v1/webhooks/wh_abc123 -H "Authorization: Bearer rcptfy_live_..."
Admin
All admin endpoints require X-Admin-Secret: your_secret. Set api_admin_secret in config.json.
POST
/v1/admin/keys/create
Create a key. Supports live and test mode. Returns api_key immediately.
Adminโ–ถ
Body
FieldDefaultDescription
owner_discord_idREQโ€”Discord user ID
labelREQโ€”e.g. John 1 Month Standard
planOPTstandardstarter | standard | pro
test_modeOPTfalsetrue = rcptfy_test_, no real emails
requests_limitOPT500Max requests
expires_daysOPTโ€”Days from now. Omit = never.
cURL
curl -X POST https://api.reciptifydev.xyz/v1/admin/keys/create   -H "X-Admin-Secret: your_secret" -H "Content-Type: application/json"   -d '{"owner_discord_id":"123456789","label":"John 1 Month","plan":"standard","requests_limit":200,"expires_days":30}'
POST
/v1/admin/keys/bulk-create
Create up to 20 keys at once. Body: {"keys": [...]}.
Adminโ–ถ
cURL
curl -X POST https://api.reciptifydev.xyz/v1/admin/keys/bulk-create   -H "X-Admin-Secret: your_secret" -H "Content-Type: application/json"   -d '{"keys":[
    {"owner_discord_id":"111","label":"User A Starter","plan":"starter","requests_limit":50,"expires_days":7},
    {"owner_discord_id":"222","label":"User B Standard","plan":"standard","requests_limit":200,"expires_days":30}
  ]}'
PATCH
/v1/admin/keys/{key}
Update label, request limit, expiry, or active status.
Adminโ–ถ
Body (all optional)
FieldDescription
labelNew label
requests_limitNew quota cap
expires_daysNew expiry from now. 0 = never.
activefalse = revoke, true = reactivate
POST
/v1/admin/keys/revoke?key=...
Immediately revoke a key.
Adminโ–ถ
cURL
curl -X POST "https://api.reciptifydev.xyz/v1/admin/keys/revoke?key=rcptfy_live_..." -H "X-Admin-Secret: your_secret"
POST
/v1/admin/keys/reactivate?key=...
Re-activate a revoked key.
Adminโ–ถ
cURL
curl -X POST "https://api.reciptifydev.xyz/v1/admin/keys/reactivate?key=rcptfy_live_..." -H "X-Admin-Secret: your_secret"
POST
/v1/admin/keys/reset-quota?key=...
Reset used count to 0.
Adminโ–ถ
cURL
curl -X POST "https://api.reciptifydev.xyz/v1/admin/keys/reset-quota?key=rcptfy_live_..." -H "X-Admin-Secret: your_secret"
GET
/v1/admin/keys
All keys, newest first.
Adminโ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/admin/keys -H "X-Admin-Secret: your_secret"
GET
/v1/admin/keys/{key}
Full details for a single key.
Adminโ–ถ
cURL
curl "https://api.reciptifydev.xyz/v1/admin/keys/rcptfy_live_..." -H "X-Admin-Secret: your_secret"
POST
/v1/admin/keys/{key}/note
Store admin-only internal notes on a key. Body: {"note":"..."}.
Adminโ–ถ
cURL
curl -X POST "https://api.reciptifydev.xyz/v1/admin/keys/rcptfy_live_.../note"   -H "X-Admin-Secret: your_secret" -H "Content-Type: application/json"   -d '{"note":"Paid via PayPal 1 June. 30 day standard plan."}'
GET
/v1/admin/keys/{key}/receipts
All receipts by a specific key. Max 500/page.
Adminโ–ถ
cURL
curl "https://api.reciptifydev.xyz/v1/admin/keys/rcptfy_live_.../receipts?limit=50" -H "X-Admin-Secret: your_secret"
GET
/v1/admin/owner/{discord_id}
All keys + recent receipts for a Discord user ID.
Adminโ–ถ
cURL
curl https://api.reciptifydev.xyz/v1/admin/owner/123456789012345678 -H "X-Admin-Secret: your_secret"
GET
/v1/admin/receipts
All receipts across all keys. Filter brand/status. Max 200/page.
Adminโ–ถ
cURL
curl "https://api.reciptifydev.xyz/v1/admin/receipts?brand=nike&status=sent&limit=50" -H "X-Admin-Secret: your_secret"
GET
/v1/admin/analytics?days=30
Global stats โ€” daily volumes, top brands, top keys, top owners. Up to 90 days.
Adminโ–ถ
cURL
curl "https://api.reciptifydev.xyz/v1/admin/analytics?days=7" -H "X-Admin-Secret: your_secret"
GET
/v1/admin/stats
Key counts, request totals, receipt totals, top brands.
Adminโ–ถ
Response
{"success":true,"request_id":"req_a1b2c3d4e5f6g7h8","data":{
  "keys":{"total":41,"active":38,"test":12,"live":26},
  "requests":{"total":9823,"today":142},
  "receipts":{"total":9801,"today":139,"sent":9650,"dry_run":151},
  "top_brands":[{"brand":"nike","count":2401},{"brand":"louisvuitton","count":1893}]
}}
POST
/v1/admin/db/cleanup
Purge old idempotency cache and dry_run receipts. Run weekly to keep DB lean.
Adminโ–ถ
cURL
curl -X POST "https://api.reciptifydev.xyz/v1/admin/db/cleanup?keep_days=7" -H "X-Admin-Secret: your_secret"
Integration Templates

Copy-paste complete working bots for each platform. Replace YOUR_API_KEY with your key from Discord.

๐Ÿ discord.py
๐ŸŸจ discord.js
โœˆ๏ธ Telegram
๐ŸŒ Website
๐Ÿ˜ PHP
bot.py โ€” discord.py ยท pip install discord.py aiohttp
import os, aiohttp, discord
from discord import app_commands
from discord.ext import commands

DISCORD_TOKEN  = os.getenv("DISCORD_TOKEN", "YOUR_DISCORD_TOKEN")
RECEIPTIFY_KEY = os.getenv("RECEIPTIFY_KEY", "rcptfy_live_YOUR_KEY")
BASE = "https://api.reciptifydev.xyz/v1"
HEADERS = {"Authorization": f"Bearer {RECEIPTIFY_KEY}"}

intents = discord.Intents.default()
bot     = commands.Bot(command_prefix="!", intents=intents)

async def api_get(path):
    async with aiohttp.ClientSession() as s:
        async with s.get(f"{BASE}{path}", headers=HEADERS) as r:
            return await r.json()

async def api_post(path, body={}, idem=None):
    h = {**HEADERS, "Content-Type": "application/json"}
    if idem: h["Idempotency-Key"] = idem
    async with aiohttp.ClientSession() as s:
        async with s.post(f"{BASE}{path}", json=body, headers=h) as r:
            return await r.json()

@bot.tree.command(name="receipt", description="Generate a brand receipt")
@app_commands.describe(brand="Brand slug e.g. nike", url="Product URL",
    price="Price e.g. 110.00", email="Recipient email",
    currency="$ ยฃ โ‚ฌ ยฅ (default ยฃ)", size="Size e.g. UK 9")
async def receipt_cmd(interaction: discord.Interaction, brand: str,
    url: str, price: str, email: str, currency: str = "ยฃ", size: str = ""):
    await interaction.response.defer(ephemeral=True)

    # Step 1: validate (free, no quota used)
    vr = await api_post("/validate", {"brand": brand, "product_url": url, "price": price, "currency": currency})
    if not vr.get("data", {}).get("valid"):
        fb = vr.get("data", {}).get("discord_feedback") or vr.get("error", {}).get("discord_feedback", "โŒ Validation failed")
        return await interaction.followup.send(fb, ephemeral=True)

    slug = vr["data"]["brand_slug"]
    name = vr["data"]["brand_info"]["name"]
    body = {"brand": slug, "product_url": url, "price": price, "currency": currency, "recipient_email": email}
    if size: body["size"] = size

    # Step 2: generate
    gr = await api_post("/generate", body, idem=f"{interaction.user.id}-{slug}-{price}")

    if gr.get("success"):
        d = gr["data"]
        e = discord.Embed(title="โœ… Receipt Generated", color=0xf97316)
        e.add_field(name="Brand",      value=name,            inline=True)
        e.add_field(name="Order #",    value=d["order_number"], inline=True)
        e.add_field(name="Sent to",    value=d["sent_to"],    inline=True)
        e.add_field(name="Product",    value=d["product_name"], inline=False)
        e.add_field(name="Price",      value=f"{currency}{price}", inline=True)
        e.add_field(name="Receipt ID", value=f"`{d['receipt_id']}`", inline=True)
        e.set_footer(text=f"Receiptify ยท {'๐Ÿงช Test' if d['test_mode'] else 'โœ… Live'}")
        await interaction.followup.send(embed=e, ephemeral=True)
    else:
        await interaction.followup.send(gr.get("error", {}).get("discord_feedback", "โŒ Failed."), ephemeral=True)

@bot.tree.command(name="brands", description="Browse supported brands")
@app_commands.describe(region="US, DE, or AUTH (optional)")
async def brands_cmd(interaction: discord.Interaction, region: str = ""):
    await interaction.response.defer(ephemeral=True)
    data = await api_get(f"/brands{'?region='+region if region else ''}")
    brands = data.get("data", {}).get("brands", [])
    desc = "
".join(f"`{b['slug']}` โ€” {b['name']}" for b in brands[:25])
    if len(brands) > 25: desc += f"
...and {len(brands)-25} more"
    e = discord.Embed(title=f"๐Ÿ“ฆ Brands ({data['data']['total']})", description=desc, color=0xf97316)
    await interaction.followup.send(embed=e, ephemeral=True)

@bot.tree.command(name="usage", description="Check your API quota")
async def usage_cmd(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    d = (await api_get("/usage")).get("data", {})
    e = discord.Embed(title="๐Ÿ“Š API Quota", color=0xf97316)
    e.add_field(name="Summary", value=d.get("discord_summary","N/A"), inline=False)
    e.add_field(name="Bar",     value=f"`{d.get('bar','')}`",         inline=False)
    await interaction.followup.send(embed=e, ephemeral=True)

@bot.tree.command(name="history", description="Your recent receipts")
@app_commands.describe(brand="Filter by brand slug (optional)")
async def history_cmd(interaction: discord.Interaction, brand: str = ""):
    await interaction.response.defer(ephemeral=True)
    data = await api_get(f"/receipts?limit=5{'&brand='+brand if brand else ''}")
    recs = data.get("data", {}).get("receipts", [])
    if not recs:
        return await interaction.followup.send("No receipts found.", ephemeral=True)
    e = discord.Embed(title="๐Ÿงพ Recent Receipts", color=0xf97316)
    for r in recs:
        e.add_field(name=f"{r['brand']} โ€” #{r['order_number']}",
            value=f"`{r['receipt_id']}` ยท {r['price']}{r['currency']} ยท {r['created_at'][:10]}", inline=False)
    await interaction.followup.send(embed=e, ephemeral=True)

@bot.tree.command(name="resend", description="Resend a receipt by ID")
@app_commands.describe(receipt_id="Receipt ID starting with rcpt_", email="Override email (optional)")
async def resend_cmd(interaction: discord.Interaction, receipt_id: str, email: str = ""):
    await interaction.response.defer(ephemeral=True)
    url_path = f"/receipts/{receipt_id}/resend" + (f"?recipient_email={email}" if email else "")
    async with aiohttp.ClientSession() as s:
        async with s.post(f"{BASE}{url_path}", headers=HEADERS) as r:
            data = await r.json()
    if data.get("success"):
        d = data["data"]
        await interaction.followup.send(f"โœ… Resent `{receipt_id}` โ€” Order #{d['order_number']}", ephemeral=True)
    else:
        await interaction.followup.send(data.get("error",{}).get("discord_feedback","โŒ Failed."), ephemeral=True)

@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"โœ… {bot.user} โ€” slash commands synced")

bot.run(DISCORD_TOKEN)
bot.js โ€” discord.js v14 ยท npm install discord.js
const { Client, GatewayIntentBits, SlashCommandBuilder, REST,
        Routes, EmbedBuilder } = require("discord.js");

const DISCORD_TOKEN  = process.env.DISCORD_TOKEN  || "YOUR_DISCORD_TOKEN";
const CLIENT_ID      = process.env.CLIENT_ID      || "YOUR_CLIENT_ID";
const RECEIPTIFY_KEY = process.env.RECEIPTIFY_KEY || "rcptfy_live_YOUR_KEY";
const BASE    = "https://api.reciptifydev.xyz/v1";
const HEADERS = { "Authorization": `Bearer ${RECEIPTIFY_KEY}`, "Content-Type": "application/json" };

async function apiGet(path) {
  return (await fetch(`${BASE}${path}`, { headers: HEADERS })).json();
}
async function apiPost(path, body = {}, idempotencyKey = null) {
  const h = { ...HEADERS };
  if (idempotencyKey) h["Idempotency-Key"] = idempotencyKey;
  return (await fetch(`${BASE}${path}`, { method:"POST", headers:h, body:JSON.stringify(body) })).json();
}

const commands = [
  new SlashCommandBuilder().setName("receipt").setDescription("Generate a brand receipt")
    .addStringOption(o => o.setName("brand").setDescription("Brand slug e.g. nike").setRequired(true))
    .addStringOption(o => o.setName("url").setDescription("Product URL").setRequired(true))
    .addStringOption(o => o.setName("price").setDescription("Price e.g. 110.00").setRequired(true))
    .addStringOption(o => o.setName("email").setDescription("Recipient email").setRequired(true))
    .addStringOption(o => o.setName("currency").setDescription("$ ยฃ โ‚ฌ ยฅ").setRequired(false))
    .addStringOption(o => o.setName("size").setDescription("Size e.g. UK 9").setRequired(false)),
  new SlashCommandBuilder().setName("brands").setDescription("List brands")
    .addStringOption(o => o.setName("region").setDescription("US | DE | AUTH").setRequired(false)),
  new SlashCommandBuilder().setName("usage").setDescription("Check API quota"),
  new SlashCommandBuilder().setName("history").setDescription("Recent receipts")
    .addStringOption(o => o.setName("brand").setDescription("Filter brand").setRequired(false)),
];

new REST().setToken(DISCORD_TOKEN).put(Routes.applicationCommands(CLIENT_ID),
  { body: commands.map(c => c.toJSON()) }).then(() => console.log("โœ… Commands registered"));

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.on("interactionCreate", async interaction => {
  if (!interaction.isChatInputCommand()) return;
  await interaction.deferReply({ ephemeral: true });
  const { commandName: cmd } = interaction;

  if (cmd === "receipt") {
    const brand    = interaction.options.getString("brand");
    const url      = interaction.options.getString("url");
    const price    = interaction.options.getString("price");
    const email    = interaction.options.getString("email");
    const currency = interaction.options.getString("currency") || "ยฃ";
    const size     = interaction.options.getString("size") || "";

    const vr = await apiPost("/validate", { brand, product_url: url, price, currency });
    if (!vr?.data?.valid) {
      return interaction.followUp({ content: vr?.data?.discord_feedback || "โŒ Validation failed", ephemeral: true });
    }
    const body = { brand: vr.data.brand_slug, product_url: url, price, currency, recipient_email: email };
    if (size) body.size = size;
    const gr = await apiPost("/generate", body, `${interaction.user.id}-${vr.data.brand_slug}-${price}`);

    if (gr?.success) {
      const d = gr.data;
      const e = new EmbedBuilder().setTitle("โœ… Receipt Generated").setColor(0xf97316)
        .addFields(
          { name:"Brand",      value:vr.data.brand_info.name, inline:true },
          { name:"Order #",    value:d.order_number,          inline:true },
          { name:"Sent to",    value:d.sent_to,               inline:true },
          { name:"Product",    value:d.product_name,          inline:false },
          { name:"Price",      value:`${currency}${price}`,   inline:true },
          { name:"Receipt ID", value:``${d.receipt_id}``,   inline:true },
        ).setFooter({ text: `Receiptify ยท ${d.test_mode ? "๐Ÿงช Test":"โœ… Live"}` });
      return interaction.followUp({ embeds: [e], ephemeral: true });
    }
    return interaction.followUp({ content: gr?.error?.discord_feedback || "โŒ Failed.", ephemeral: true });
  }

  if (cmd === "brands") {
    const region = interaction.options.getString("region") || "";
    const data   = await apiGet(`/brands${region ? "?region="+region : ""}`);
    const lines  = data.data.brands.slice(0,25).map(b => ``${b.slug}` โ€” ${b.name}`).join("
");
    return interaction.followUp({
      embeds: [new EmbedBuilder().setTitle(`๐Ÿ“ฆ Brands (${data.data.total})`).setDescription(lines).setColor(0xf97316)],
      ephemeral: true
    });
  }

  if (cmd === "usage") {
    const d = (await apiGet("/usage")).data;
    return interaction.followUp({
      embeds: [new EmbedBuilder().setTitle("๐Ÿ“Š Quota").setColor(0xf97316)
        .addFields({ name:"Summary", value:d.discord_summary, inline:false },
                   { name:"Bar",     value:``${d.bar}``,    inline:false })],
      ephemeral: true
    });
  }

  if (cmd === "history") {
    const brand = interaction.options.getString("brand") || "";
    const data  = await apiGet(`/receipts?limit=5${brand ? "&brand="+brand : ""}`);
    const recs  = data.data.receipts;
    if (!recs.length) return interaction.followUp({ content:"No receipts.", ephemeral:true });
    const e = new EmbedBuilder().setTitle("๐Ÿงพ Receipt History").setColor(0xf97316);
    recs.forEach(r => e.addFields({ name:`${r.brand} โ€” #${r.order_number}`,
      value:``${r.receipt_id}` ยท ${r.price}${r.currency} ยท ${r.created_at?.slice(0,10)}`, inline:false }));
    return interaction.followUp({ embeds: [e], ephemeral: true });
  }
});

client.once("ready", () => console.log(`โœ… Logged in as ${client.user.tag}`));
client.login(DISCORD_TOKEN);
bot.py โ€” python-telegram-bot v20+ ยท pip install python-telegram-bot aiohttp
import os, aiohttp
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, ConversationHandler, filters, ContextTypes

TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "YOUR_TELEGRAM_TOKEN")
RECEIPTIFY_KEY = os.getenv("RECEIPTIFY_KEY", "rcptfy_live_YOUR_KEY")
BASE    = "https://api.reciptifydev.xyz/v1"
HEADERS = {"Authorization": f"Bearer {RECEIPTIFY_KEY}"}
BRAND, URL, PRICE, EMAIL, SIZE = range(5)

async def api_post(path, body={}, idem=None):
    h = {**HEADERS, "Content-Type": "application/json"}
    if idem: h["Idempotency-Key"] = idem
    async with aiohttp.ClientSession() as s:
        async with s.post(f"{BASE}{path}", json=body, headers=h) as r:
            return await r.json()

async def api_get(path):
    async with aiohttp.ClientSession() as s:
        async with s.get(f"{BASE}{path}", headers=HEADERS) as r:
            return await r.json()

async def start(u: Update, _):
    await u.message.reply_text(
        "๐Ÿ‘‹ *Receiptify Bot*

"
        "/receipt โ€” Generate a receipt
"
        "/brands โ€” Browse brands
"
        "/usage โ€” Check your quota
"
        "/history โ€” Recent receipts",
        parse_mode="Markdown"
    )

async def brands(u: Update, ctx):
    region = ctx.args[0].upper() if ctx.args else ""
    d = await api_get(f"/brands{'?region='+region if region else ''}")
    lines = "
".join(f"`{b['slug']}` โ€” {b['name']}" for b in d["data"]["brands"][:25])
    await u.message.reply_text(f"๐Ÿ“ฆ *Brands ({d['data']['total']})*

{lines}", parse_mode="Markdown")

async def usage(u: Update, _):
    d = (await api_get("/usage"))["data"]
    await u.message.reply_text(
        f"๐Ÿ“Š *API Quota*

{d['discord_summary']}
`{d['bar']}`",
        parse_mode="Markdown"
    )

async def history(u: Update, _):
    d = await api_get("/receipts?limit=5")
    recs = d["data"]["receipts"]
    if not recs: return await u.message.reply_text("No receipts found.")
    lines = [f"*{r['brand']}* โ€” #{r['order_number']}
`{r['receipt_id']}` ยท {r['price']}{r['currency']} ยท {r['created_at'][:10]}" for r in recs]
    await u.message.reply_text("๐Ÿงพ *Recent Receipts*

" + "

".join(lines), parse_mode="Markdown")

async def receipt_start(u: Update, _):
    await u.message.reply_text("What *brand*? e.g. `nike`, `louisvuitton`
/cancel to stop.", parse_mode="Markdown")
    return BRAND

async def get_brand(u: Update, ctx):
    ctx.user_data["brand"] = u.message.text.strip().lower()
    await u.message.reply_text("Paste the full *product URL*:", parse_mode="Markdown")
    return URL

async def get_url(u: Update, ctx):
    ctx.user_data["url"] = u.message.text.strip()
    await u.message.reply_text("*Price*? e.g. `110.00` (no symbol):", parse_mode="Markdown")
    return PRICE

async def get_price(u: Update, ctx):
    ctx.user_data["price"] = u.message.text.strip()
    await u.message.reply_text("*Email* to receive receipt:", parse_mode="Markdown")
    return EMAIL

async def get_email(u: Update, ctx):
    ctx.user_data["email"] = u.message.text.strip()
    await u.message.reply_text("*Size*? e.g. `UK 9` โ€” or `-` to skip:", parse_mode="Markdown")
    return SIZE

async def get_size(u: Update, ctx):
    size  = u.message.text.strip()
    brand = ctx.user_data["brand"]
    url   = ctx.user_data["url"]
    price = ctx.user_data["price"]
    email = ctx.user_data["email"]
    uid   = u.effective_user.id
    await u.message.reply_text("โณ Generating...")

    vr = await api_post("/validate", {"brand": brand, "product_url": url, "price": price})
    if not vr["data"]["valid"]:
        fb = vr["data"]["discord_feedback"].replace("**", "*")
        await u.message.reply_text(fb, parse_mode="Markdown")
        return ConversationHandler.END

    slug = vr["data"]["brand_slug"]
    body = {"brand": slug, "product_url": url, "price": price, "currency": "ยฃ", "recipient_email": email}
    if size and size != "-": body["size"] = size
    gr = await api_post("/generate", body, idem=f"tg-{uid}-{slug}-{price}")

    if gr["success"]:
        d = gr["data"]
        await u.message.reply_text(
            f"โœ… *Receipt Sent!*

"
            f"๐Ÿท Brand: *{d['brand']}*
"
            f"๐Ÿ”ข Order: `{d['order_number']}`
"
            f"๐Ÿ“ฆ Product: {d['product_name']}
"
            f"๐Ÿ“ง Sent to: `{d['sent_to']}`
"
            f"๐Ÿช™ ID: `{d['receipt_id']}`",
            parse_mode="Markdown"
        )
    else:
        await u.message.reply_text(gr["error"]["discord_feedback"].replace("**","*"), parse_mode="Markdown")
    return ConversationHandler.END

async def cancel(u: Update, _):
    await u.message.reply_text("Cancelled.")
    return ConversationHandler.END

def main():
    app = Application.builder().token(TELEGRAM_TOKEN).build()
    conv = ConversationHandler(
        entry_points=[CommandHandler("receipt", receipt_start)],
        states={
            BRAND: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_brand)],
            URL:   [MessageHandler(filters.TEXT & ~filters.COMMAND, get_url)],
            PRICE: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_price)],
            EMAIL: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_email)],
            SIZE:  [MessageHandler(filters.TEXT & ~filters.COMMAND, get_size)],
        },
        fallbacks=[CommandHandler("cancel", cancel)],
    )
    app.add_handler(CommandHandler("start",   start))
    app.add_handler(CommandHandler("brands",  brands))
    app.add_handler(CommandHandler("usage",   usage))
    app.add_handler(CommandHandler("history", history))
    app.add_handler(conv)
    print("โœ… Telegram bot running...")
    app.run_polling()

if __name__ == "__main__":
    main()
widget.html โ€” drop-in widget, vanilla JS, no dependencies
<!-- Receiptify Widget โ€” replace rcptfy_live_YOUR_KEY -->
<div id="rcw">
<style>
  #rcw *{box-sizing:border-box;font-family:system-ui,sans-serif}
  #rcf{background:#0c0c0c;border:1px solid #1e1e1e;border-radius:12px;padding:24px;max-width:420px;margin:0 auto}
  #rcf h2{font-size:17px;font-weight:700;color:#fafafa;margin-bottom:3px}
  #rcf p{font-size:12px;color:#52525b;margin-bottom:18px}
  .rr{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  .rf{margin-bottom:12px}
  .rf label{display:block;font-size:10px;font-weight:600;color:#a1a1aa;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px}
  .rf input,.rf select{width:100%;background:#080808;border:1px solid #1e1e1e;border-radius:6px;padding:8px 10px;font-size:12px;color:#fafafa;outline:none;transition:border .15s}
  .rf input:focus,.rf select:focus{border-color:#f97316}
  .rf select option{background:#0c0c0c}
  #rcb{width:100%;background:#f97316;color:#fff;font-size:13px;font-weight:700;border:none;border-radius:8px;padding:11px;cursor:pointer;transition:background .15s}
  #rcb:hover{background:#ea580c}#rcb:disabled{background:#27272a;cursor:not-allowed}
  #rcm{margin-top:12px;padding:10px 13px;border-radius:7px;font-size:12px;display:none;line-height:1.5}
  #rcm.ok{background:rgba(34,197,94,.08);border:1px solid rgba(34,197,94,.2);color:#4ade80}
  #rcm.err{background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);color:#f87171}
</style>
<form id="rcf" onsubmit="rcSubmit(event)">
  <h2>๐Ÿงพ Generate Receipt</h2>
  <p>Enter your details and get a receipt in seconds.</p>
  <div class="rf"><label>Brand</label><input id="rcbrand" placeholder="e.g. nike" required/></div>
  <div class="rf"><label>Product URL</label><input id="rcurl" type="url" placeholder="https://..." required/></div>
  <div class="rr">
    <div class="rf"><label>Price</label><input id="rcprice" placeholder="110.00" required/></div>
    <div class="rf"><label>Currency</label>
      <select id="rccurr"><option value="ยฃ">ยฃ GBP</option><option value="$">$ USD</option>
        <option value="โ‚ฌ">โ‚ฌ EUR</option><option value="ยฅ">ยฅ JPY</option></select></div>
  </div>
  <div class="rr">
    <div class="rf"><label>Email</label><input id="rcemail" type="email" placeholder="you@gmail.com" required/></div>
    <div class="rf"><label>Size (optional)</label><input id="rcsize" placeholder="UK 9"/></div>
  </div>
  <button type="submit" id="rcb">Generate Receipt</button>
  <div id="rcm"></div>
</form>
<script>
const KEY  = "rcptfy_live_YOUR_KEY";
const BASE = "https://api.reciptifydev.xyz/v1";
const H    = { "Authorization": `Bearer ${KEY}`, "Content-Type": "application/json" };

async function post(p, b) {
  return (await fetch(`${BASE}${p}`, { method:"POST", headers:H, body:JSON.stringify(b) })).json();
}
function msg(t, type) {
  const el = document.getElementById("rcm");
  el.innerHTML = t; el.className = type; el.style.display = "block";
}
async function rcSubmit(e) {
  e.preventDefault();
  const btn   = document.getElementById("rcb");
  const brand = document.getElementById("rcbrand").value.trim();
  const url   = document.getElementById("rcurl").value.trim();
  const price = document.getElementById("rcprice").value.trim();
  const curr  = document.getElementById("rccurr").value;
  const email = document.getElementById("rcemail").value.trim();
  const size  = document.getElementById("rcsize").value.trim();
  btn.disabled = true; btn.textContent = "Validating...";
  const vr = await post("/validate", { brand, product_url:url, price, currency:curr });
  if (!vr?.data?.valid) {
    msg("โŒ " + Object.values(vr?.data?.issues||{}).join("<br>"), "err");
    btn.disabled = false; btn.textContent = "Generate Receipt"; return;
  }
  btn.textContent = "Generating...";
  const body = { brand:vr.data.brand_slug, product_url:url, price, currency:curr, recipient_email:email };
  if (size) body.size = size;
  const gr = await post("/generate", body);
  if (gr?.success) {
    const d = gr.data;
    msg(`โœ… <b>Receipt sent!</b><br>Order #${d.order_number} ยท ${d.product_name}<br>Sent to ${d.sent_to}`, "ok");
  } else {
    msg("โŒ " + (gr?.error?.message||"Generation failed."), "err");
  }
  btn.disabled = false; btn.textContent = "Generate Receipt";
}
</script>
</div>
receiptify.php โ€” pure PHP, no dependencies
<?php
/**
 * Receiptify PHP Integration
 * Requires: PHP 7.4+, curl extension
 * Usage: include this file and call Receiptify::generate(...)
 */

class Receiptify {
    const API_KEY = "rcptfy_live_YOUR_KEY";
    const BASE    = "https://api.reciptifydev.xyz/v1";

    private static function call(string $method, string $path, array $body = [], string $idem = null): array {
        $headers = [
            "Authorization: Bearer " . self::API_KEY,
            "Content-Type: application/json",
        ];
        if ($idem) $headers[] = "Idempotency-Key: $idem";

        $ch = curl_init(self::BASE . $path);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_POSTFIELDS     => $body ? json_encode($body) : null,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 30,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);
        $res = json_decode(curl_exec($ch), true);
        curl_close($ch);
        return $res ?? ["success" => false, "error" => ["message" => "curl failed"]];
    }

    public static function validate(string $brand, string $url, string $price): array {
        return self::call("POST", "/validate", compact("brand") + ["product_url" => $url, "price" => $price]);
    }

    public static function generate(string $brand, string $url, string $price,
        string $email, string $currency = "ยฃ", string $size = "", string $externalId = ""): array {
        $body = ["brand" => $brand, "product_url" => $url, "price" => $price,
                 "currency" => $currency, "recipient_email" => $email];
        if ($size)       $body["size"]        = $size;
        if ($externalId) $body["external_id"] = $externalId;
        $idem = "php-" . md5($brand . $price . $email);
        return self::call("POST", "/generate", $body, $idem);
    }

    public static function usage(): array {
        return self::call("GET", "/usage");
    }

    public static function brands(string $region = ""): array {
        return self::call("GET", "/brands" . ($region ? "?region=$region" : ""));
    }

    public static function receipts(int $limit = 10, string $brand = ""): array {
        $q = "?limit=$limit" . ($brand ? "&brand=$brand" : "");
        return self::call("GET", "/receipts" . $q);
    }
}

/* โ”€โ”€โ”€ Usage examples โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Validate first (free)
$vr = Receiptify::validate("nike", "https://www.nike.com/...", "110.00");
if (!$vr["data"]["valid"]) {
    die($vr["data"]["discord_feedback"]);  // human-readable error
}

// Generate
$gr = Receiptify::generate(
    brand:       "nike",
    url:         "https://www.nike.com/t/air-force-1-07/CW2288-111",
    price:       "110.00",
    email:       "buyer@gmail.com",
    currency:    "ยฃ",
    size:        "UK 9",
    externalId:  "order_" . uniqid()
);

if ($gr["success"]) {
    $d = $gr["data"];
    echo "โœ… Receipt sent! Order #{$d['order_number']} โ€” {$d['product_name']}";
} else {
    echo "โŒ " . $gr["error"]["message"];
}

// Check quota
$usage = Receiptify::usage();
echo $usage["data"]["discord_summary"];  // "**453** requests left out of **500**"
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
Error Reference

Every error has a stable error.code and a error.discord_feedback string ready to forward to Discord/Telegram users.

codeHTTPMeaning & fix
invalid_api_key401Key not found โ€” check it starts with rcptfy_live_ or rcptfy_test_
key_revoked403Key revoked โ€” contact support
key_expired403Key expired โ€” purchase renewal on Discord
rate_limit_exceeded429Quota exhausted โ€” upgrade or wait for reset
no_account400No account set up โ€” run /generate in Discord bot first
no_email400No email on account โ€” set via /generate โ†’ Settings
invalid_brand400Slug not found โ€” use GET /v1/brands for valid slugs
invalid_region400Region must be US, DE, or AUTH
invalid_webhook_url400Webhook URL must be https://
validation_error422Field failed โ€” check error.details for per-field errors
template_missing500Template not on server โ€” contact support with request_id
email_send_failed500Transient โ€” retry once with same Idempotency-Key
webhook_delivery_failed502Your URL didn't respond 2xx within 5s
not_found404Resource not found or not yours
batch_too_large400Max 5 for /batch, 20 for /bulk-create
no_changes400PATCH body had no fields
forbidden403Wrong or missing X-Admin-Secret
internal_error500Unexpected โ€” include request_id when contacting support
Rate Limits

Per-key, resets at midnight UTC. Quota-free endpoints (validate, preview, usage, analytics, receipt history, brands) never consume a request. Every response includes:

HeaderDescription
X-RateLimit-LimitTotal requests on your plan
X-RateLimit-RemainingRemaining requests
X-RateLimit-ResetUnix timestamp โ€” next reset (midnight UTC)
X-Response-TimeServer processing time e.g. 342.1ms
X-Idempotent-Replayedtrue when response was served from idempotency cache
Idempotency
Always set Idempotency-Key on POST /v1/generate.
Use any unique string per order โ€” e.g. user ID + brand + price. If a request times out or fails, retry with the same key โ€” we replay the cached response without sending a duplicate email.

Keys stored 24 hours. 5xx responses not cached. Same key with different body returns 409.
Webhook Events
EventWhen firedPayload includes
receipt.sentAfter every successful email sendreceipt_id, brand, order_number, sent_to, timestamp
receipt.dry_runAfter every dry_run requestreceipt_id, brand, order_number, timestamp
quota.warningWhen usage reaches 80%, 90%, 100% of quotathreshold_pct, used, limit, remaining