API Documentation
Complete reference for the WalletWallet API. Generate signed Apple Wallet passes with a single HTTP request.
https://api.walletwallet.dev Overview
The WalletWallet API lets you generate signed Apple Wallet (.pkpass) files programmatically. Send a POST request with your pass data and receive a ready-to-distribute pass file.
All requests use JSON bodies and return JSON responses, except the pass creation endpoint which returns a binary .pkpass file on success.
Quick start
- Get your API key (instant, no credit card)
- Make a POST request to
/api/pkpass - Save the response as a
.pkpassfile - Distribute to your users via email, web, or AirDrop
Authentication
All API requests require a valid API key. Include it in the Authorization header using the Bearer scheme:
Authorization: Bearer ww_live_<your_key>
API keys follow the format ww_live_ followed by 32 hexadecimal characters. You can get one instantly from the signup page.
/api/auth/usage
Returns your current monthly usage statistics. Requires authentication.
Headers
| Header | Value |
|---|---|
| Authorization | Bearer ww_live_<your_key> |
Response
{
"count": 150,
"limit": 1000,
"remaining": 850,
"resetDate": "2026-03-01",
"plan": "free"
} curl https://api.walletwallet.dev/api/auth/usage \
-H "Authorization: Bearer ww_live_<your_key>" /api/pkpass
Generates a signed Apple Wallet pass (.pkpass file). This is the primary endpoint. Requires authentication.
Headers
| Header | Value |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer ww_live_<your_key> |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| barcodeValue | string | Yes | Data encoded in the barcode. Max 512 characters. |
| barcodeFormat | string | Yes | QR PDF417 Aztec Code128 |
| title | string | Yes | Pass title shown on the card. Max 64 characters. |
| label | string | No | Label text above the value field. Must be provided together with value. |
| value | string | No | Value text below the label. Must be provided together with label. |
| colorPreset | string | No |
Color theme:
dark blue green red purple orange.
Defaults to dark.
|
| expirationDays | number | No |
Pass expires after this many days:
30,
90, or
365 |
| color Pro | string | No | Custom hex background color, e.g. #1e40af. Overrides colorPreset. |
| logoURL Pro | string | No | Custom logo image. Must use HTTPS — HTTP URLs are rejected. Also accepts PNG data URIs (data:image/png;base64,...). Private/internal addresses are not allowed. |
Response
application/vnd.apple.pkpass file. Save as .pkpass.
curl -X POST https://api.walletwallet.dev/api/pkpass \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ww_live_<your_key>" \
-d '{
"barcodeValue": "MEMBER-12345",
"barcodeFormat": "QR",
"title": "Membership Card"
}' \
-o membership.pkpass curl -X POST https://api.walletwallet.dev/api/pkpass \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ww_live_<your_key>" \
-d '{
"barcodeValue": "LOYALTY-98765",
"barcodeFormat": "QR",
"title": "Coffee Rewards",
"label": "Member",
"value": "Gold Status",
"colorPreset": "green",
"expirationDays": 365
}' \
-o rewards.pkpass curl -X POST https://api.walletwallet.dev/api/pkpass \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ww_live_<your_key>" \
-d '{
"barcodeValue": "VIP-001",
"barcodeFormat": "QR",
"title": "VIP Access",
"color": "#8B4513",
"logoURL": "https://example.com/logo.png"
}' \
-o vip.pkpass Barcode Formats
| Format | Type | Best For |
|---|---|---|
| QR | 2D square | General purpose, high data capacity, most common |
| PDF417 | 2D stacked | Boarding passes, ID cards, government documents |
| Aztec | 2D square | Transit tickets, compact spaces, no quiet zone needed |
| Code128 | 1D linear | Retail, inventory, shipping labels |
Color Presets
Available on all plans. Use the colorPreset field.
Pro plan: Use the color field with any hex value (e.g. #1e40af) to set a fully custom background color.
Rate Limits
| Plan | Passes / Month | Custom Color | Custom Logo | Price |
|---|---|---|---|---|
| Free | 1,000 | No | No | $0 |
| Pro | 100,000 | Yes | Yes | $19/mo |
Usage resets on the 1st of each month (UTC). You can check your current usage at any time via the /api/auth/usage endpoint.
When you exceed your limit, the API returns a 429 response with the reset date:
{
"error": "Rate limit exceeded",
"resetDate": "2026-03-01",
"message": "Monthly limit reached. Resets on 2026-03-01"
} Errors
All error responses return JSON with an error field:
{
"error": "Error message describing the issue"
} HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad request — invalid input, malformed JSON, or validation failure |
| 401 | Unauthorized — missing or invalid API key |
| 404 | Not found — endpoint does not exist |
| 405 | Method not allowed — wrong HTTP method |
| 429 | Rate limit exceeded — monthly quota used up |
| 500 | Internal server error |
Common Validation Errors
| Cause | Error Message |
|---|---|
| Missing required field | barcodeValue is required |
| Invalid barcode format | barcodeFormat must be one of: QR, PDF417, Aztec, Code128 |
| Title too long | title must be 64 characters or less |
| Label without value | label and value must both be provided together |
| Custom color on free plan | color is only available on the Pro plan |
| Invalid expiration | expirationDays must be 30, 90, or 365 |
Code Examples
const response = await fetch('https://api.walletwallet.dev/api/pkpass', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ww_live_<your_key>'
},
body: JSON.stringify({
barcodeValue: 'TICKET-789',
barcodeFormat: 'QR',
title: 'Event Ticket',
label: 'Seat',
value: 'A-23'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
// Browser: trigger download
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'ticket.pkpass';
a.click();
// Node.js: save to file
// const fs = require('fs');
// const buffer = Buffer.from(await response.arrayBuffer());
// fs.writeFileSync('ticket.pkpass', buffer); import requests
response = requests.post(
'https://api.walletwallet.dev/api/pkpass',
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer ww_live_<your_key>'
},
json={
'barcodeValue': 'ORDER-456',
'barcodeFormat': 'Code128',
'title': 'Order Pickup',
'label': 'Order #',
'value': '456'
}
)
response.raise_for_status()
with open('order.pkpass', 'wb') as f:
f.write(response.content) require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.walletwallet.dev/api/pkpass')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer ww_live_<your_key>'
request.body = {
barcodeValue: 'MEMBER-001',
barcodeFormat: 'QR',
title: 'Gym Membership',
label: 'Member',
value: 'Premium'
}.to_json
response = http.request(request)
File.binwrite('membership.pkpass', response.body) $ch = curl_init('https://api.walletwallet.dev/api/pkpass');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ww_live_<your_key>'
],
CURLOPT_POSTFIELDS => json_encode([
'barcodeValue' => 'COUPON-50OFF',
'barcodeFormat' => 'QR',
'title' => 'Discount Coupon',
'label' => 'Discount',
'value' => '50% Off'
])
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
file_put_contents('coupon.pkpass', $response);
} package main
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]interface{}{
"barcodeValue": "PASS-999",
"barcodeFormat": "QR",
"title": "Access Pass",
})
req, _ := http.NewRequest("POST", "https://api.walletwallet.dev/api/pkpass", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer ww_live_<your_key>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := os.Create("access.pkpass")
defer out.Close()
io.Copy(out, resp.Body)
} Testing Your Pass
- Save the API response to a
.pkpassfile - macOS: Double-click the file to preview in Finder
- iOS: AirDrop or email the file to your device
- The pass will prompt to add to Apple Wallet
Tip: Passes are signed with Apple certificates, so they work immediately on any iOS device — no additional configuration needed.
Free plan includes 1,000 passes/month