SDK examples — Node, Python, PHP
Submit an order in four languages — paste, swap in your tenant ID, and you have a working integration.
Overview
These snippets target the public order submit endpoint — the most-asked-about integration in production. Every example sends the same payload; the only thing that changes is the HTTP client.
The endpoint is anonymous-safe (no auth header required); coupon codes, gift cards, and the Phase 55 customerCoords field are all optional. See api/orders for the full body schema.
openapi-generator.cURL
curl -X POST 'https://restora360.com/api/public/luigi-pizza/orders' \
-H 'Content-Type: application/json' \
-d '{
"type": "delivery",
"customerName": "Jane Doe",
"customerEmail": "jane@example.com",
"customerPhone": "+44 7700 900123",
"deliveryAddress": "12 Example Street",
"postcode": "SW1A 1AA",
"paymentMethod": "cash",
"items": [
{ "productId": "p_abc123", "productName": "Margherita pizza", "quantity": 1, "price": 12.5 }
]
}'The server re-validates every item price against the menu — your local price field is informational, not authoritative.
Node.js (fetch)
Works on Node 18+ (native fetch) and any recent Bun / Deno runtime. No dependencies.
const TENANT_ID = 'luigi-pizza';
async function submitOrder() {
const response = await fetch(`https://restora360.com/api/public/${TENANT_ID}/orders`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'delivery',
customerName: 'Jane Doe',
customerEmail: 'jane@example.com',
customerPhone: '+44 7700 900123',
deliveryAddress: '12 Example Street',
postcode: 'SW1A 1AA',
paymentMethod: 'cash',
items: [
{ productId: 'p_abc123', productName: 'Margherita pizza', quantity: 1, price: 12.5 },
],
}),
});
if (!response.ok) {
const error = await response.json();
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Rate limited — retry in ${retryAfter}s`);
}
throw new Error(error.message ?? error.error);
}
return response.json();
}
submitOrder().then(console.log).catch(console.error);Python (requests)
Tested against Python 3.10+ and requests 2.31. Install with pip install requests.
import requests
TENANT_ID = "luigi-pizza"
BASE_URL = "https://restora360.com"
def submit_order():
response = requests.post(
f"{BASE_URL}/api/public/{TENANT_ID}/orders",
json={
"type": "delivery",
"customerName": "Jane Doe",
"customerEmail": "jane@example.com",
"customerPhone": "+44 7700 900123",
"deliveryAddress": "12 Example Street",
"postcode": "SW1A 1AA",
"paymentMethod": "cash",
"items": [
{
"productId": "p_abc123",
"productName": "Margherita pizza",
"quantity": 1,
"price": 12.5,
}
],
},
timeout=10,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After", "60")
raise RuntimeError(f"Rate limited — retry in {retry_after}s")
response.raise_for_status()
return response.json()
if __name__ == "__main__":
print(submit_order())PHP (cURL)
No Composer dependencies — uses the cURL extension shipped with every PHP install.
<?php
const TENANT_ID = 'luigi-pizza';
const BASE_URL = 'https://restora360.com';
function submitOrder(): array {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => BASE_URL . '/api/public/' . TENANT_ID . '/orders',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'type' => 'delivery',
'customerName' => 'Jane Doe',
'customerEmail' => 'jane@example.com',
'customerPhone' => '+44 7700 900123',
'deliveryAddress' => '12 Example Street',
'postcode' => 'SW1A 1AA',
'paymentMethod' => 'cash',
'items' => [
['productId' => 'p_abc123', 'productName' => 'Margherita pizza', 'quantity' => 1, 'price' => 12.5],
],
]),
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code === 429) {
throw new RuntimeException('Rate limited — retry later');
}
if ($code >= 400) {
throw new RuntimeException("HTTP $code: $body");
}
return json_decode($body, true);
}
print_r(submitOrder());Next steps
- Idempotency — set the
Idempotency-Keyheader to a stable per-attempt UUID so network retries don't double-submit. See api/idempotency. - Polling order status — pass the returned
order.idtoGET /api/public/{tenantId}/orders/{orderId}. Webhook events are the better long-term path; see api-webhooks/overview. - Rate limits — back off on
Retry-Afterrather than retrying immediately; the limiter slides forward in real time, not by fixed buckets. See api/rate-limits.
Frequently asked
- The tenant ID is the subdomain (e.g. `luigi-pizza` for `luigi-pizza.restora360.com`). Operators can find theirs in **Settings → Domain** in the dashboard.
- Auto-generated SDKs go stale fast. The OpenAPI spec is our single source of truth — generate the SDK you actually want with `openapi-generator-cli` in your build pipeline.
- Demo tenants accept structurally-valid requests but reject real orders with a 403 `DEMO_MODE_ACTIVE`. Production tenants share the same endpoints — there is no separate sandbox host.
Related articles
API Documentation
API — getting started
Five-minute primer so you can make your first API call.
API Documentation
Orders API
The endpoints you call to drive a customer order through the kitchen.
API Documentation
Idempotency
You can safely retry network failures without double-charging customers or double-creating orders.