Authentication
All API requests require an API key. Include your key in the
X-API-Key
header or as an
Authorization: Bearer
token.
curl https://grailguard.io/api/v1/quote \ -H "X-API-Key: gg_abc123def456-78901234abcdef5678"
API keys follow the format
keyId-keySecret
. The key ID is the first segment (before the hyphen); the secret is everything after. Keep your full key confidential.
Rate Limits
Each API key has a per-minute rate limit (default: 60 requests/min). When exceeded, the API returns
429 Too Many Requests
. The response includes a
Retry-After
header with the number of seconds to wait.
{
"error": "Rate limit exceeded. Try again in 42s"
}
Error Handling
The API uses standard HTTP status codes. All error responses include a JSON body with an
error
field.
| Code | Meaning |
|---|---|
400
|
Bad Request — missing or invalid parameters |
401
|
Unauthorized — missing or invalid API key |
403
|
Forbidden — key lacks the required scope |
404
|
Not Found — resource does not exist |
429
|
Rate limit exceeded |
500
|
Internal server error |
Endpoints
Get a shipping quote based on origin, destination, and item value. Quotes are valid for 24 hours.
Query Parameters
| Param | Type | Description |
|---|---|---|
pickup_zip
required
|
string | Origin ZIP / postal code |
delivery_zip
required
|
string | Destination ZIP / postal code |
declared_value
|
number | Item value in USD (default: 5000) |
service_tier
|
string |
metro
,
standard
, or
international
(default: standard)
|
Example
curl "https://grailguard.io/api/v1/quote?pickup_zip=10001&delivery_zip=90210&declared_value=25000&service_tier=standard" \ -H "X-API-Key: YOUR_API_KEY"
{
"quote": {
"baseFee": 400,
"insurancePremium": 375,
"total": 775,
"currency": "USD",
"serviceTier": "standard",
"declaredValue": 25000,
"estimatedTransit": "1-3 business days",
"validFor": "24 hours"
}
}
Create a new delivery booking. Returns a tracking number and tracking URL.
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
customer_name
required
|
string | Full name of the sender |
customer_email
required
|
string | Email for delivery notifications |
customer_phone
|
string | Phone number |
pickup_address
required
|
string | Full pickup address |
delivery_address
required
|
string | Full delivery address |
item_description
|
string | Description of the item |
declared_value
|
number | Item value in USD for insurance |
service_tier
|
string |
metro
,
standard
, or
international
|
stripe_payment_id
|
string | Stripe PaymentIntent ID if pre-paid |
Example
curl -X POST https://grailguard.io/api/v1/bookings \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"pickup_address": "123 Main St, New York, NY 10001",
"delivery_address": "456 Oak Ave, Los Angeles, CA 90210",
"item_description": "Patek Philippe Nautilus 5711",
"declared_value": 125000,
"service_tier": "standard"
}'
{
"booking": {
"id": 42,
"trackingNumber": "GG-2026-A7X9K3",
"status": "confirmed",
"trackingUrl": "https://grailguard.io/track.html?tn=GG-2026-A7X9K3",
"createdAt": "2026-04-15T14:30:00.000Z"
}
}
Retrieve full booking details including all tracking events.
Example
curl https://grailguard.io/api/v1/bookings/GG-2026-A7X9K3 \ -H "X-API-Key: YOUR_API_KEY"
{
"booking": {
"trackingNumber": "GG-2026-A7X9K3",
"status": "in_transit",
"serviceTier": "standard",
"customer": { "name": "Jane Doe", "email": "jane@example.com" },
"pickup": "123 Main St, New York, NY 10001",
"delivery": "456 Oak Ave, Los Angeles, CA 90210",
"item": { "description": "Patek Philippe Nautilus 5711", "declaredValue": 125000 },
"courier": "Alex Rivera",
"payment": { "status": "paid", "amount": 1900 },
"scheduledDate": "2026-04-16",
"createdAt": "2026-04-15T14:30:00.000Z",
"updatedAt": "2026-04-15T18:00:00.000Z",
"trackingUrl": "https://grailguard.io/track.html?tn=GG-2026-A7X9K3"
},
"events": [
{ "status": "Booking Confirmed", "location": "New York, NY", "description": "Booking created", "timestamp": "2026-04-15T14:30:00.000Z" },
{ "status": "Courier Assigned", "location": "New York, NY", "description": "Courier Alex Rivera assigned", "timestamp": "2026-04-15T15:00:00.000Z" },
{ "status": "Picked Up", "location": "123 Main St, New York, NY", "description": "Item collected", "timestamp": "2026-04-16T09:00:00.000Z" }
]
}
List all bookings with pagination and optional status filtering.
Query Parameters
| Param | Type | Description |
|---|---|---|
page
|
int | Page number (default: 1) |
limit
|
int | Results per page, max 100 (default: 25) |
status
|
string |
Filter by status (e.g.
confirmed
,
in_transit
,
delivered
)
|
Example
curl "https://grailguard.io/api/v1/bookings?page=1&limit=10&status=confirmed" \ -H "X-API-Key: YOUR_API_KEY"
{
"bookings": [
{
"trackingNumber": "GG-2026-A7X9K3",
"customer": "Jane Doe",
"status": "confirmed",
"tier": "standard",
"amount": 775,
"createdAt": "2026-04-15T14:30:00.000Z",
"updatedAt": "2026-04-15T14:30:00.000Z"
}
],
"total": 1,
"page": 1,
"pages": 1
}
Webhooks
Subscribe to real-time events so your system is notified when booking statuses change.
Register a new webhook subscription. Returns a signing secret for payload verification.
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
url
required
|
string | HTTPS endpoint to receive events |
events
|
string |
Comma-separated event types (default:
booking.status_changed
)
|
booking.created
,
booking.status_changed
,
booking.delivered
,
booking.cancelled
Example
curl -X POST https://grailguard.io/api/v1/webhooks \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://your-app.com/webhooks/grailguard", "events": "booking.status_changed,booking.delivered" }'
{
"webhook": {
"id": 7,
"url": "https://your-app.com/webhooks/grailguard",
"events": ["booking.status_changed", "booking.delivered"],
"secret": "a1b2c3d4e5f6...",
"note": "Save this secret - we use it to sign webhook payloads via HMAC-SHA256 in the X-GrailGuard-Signature header."
}
}
List all active webhook subscriptions for your API key.
Remove a webhook subscription by ID.
Scopes & Permissions
API keys are assigned scopes that control which endpoints they can access. Scopes are cumulative — a key with
admin
scope also has
write
and
read
access.
| Scope | Endpoints | Use Case |
|---|---|---|
read
|
GET /quote, GET /bookings, GET /bookings/:tn | Tracking integrations, dashboards |
write
|
POST /bookings + all read endpoints | Booking creation (e-commerce, platforms) |
admin
|
All endpoints including webhooks | Full integration partners |
Verifying Webhook Signatures
Every webhook POST includes an
X-GrailGuard-Signature
header containing an HMAC-SHA256 hash of the JSON payload, signed with the secret returned when you created the subscription.
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express handler
app.post('/webhooks/grailguard', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-grailguard-signature'];
if (!verifyWebhook(req.body, sig, process.env.GG_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Event:', event.type, event.data);
res.sendStatus(200);
});
crypto.timingSafeEqual
to prevent timing attacks.
Webhook Payload Format
{
"type": "booking.status_changed",
"timestamp": "2026-04-15T18:00:00.000Z",
"data": {
"trackingNumber": "GG-2026-A7X9K3",
"previousStatus": "confirmed",
"newStatus": "in_transit",
"courier": "Alex Rivera",
"updatedAt": "2026-04-15T18:00:00.000Z"
}
}