API Reference & JS SDK

Build on Dineator

The complete API reference and JavaScript SDK for integrating Dineator into your apps — from ordering flows and menu management to themes and analytics.

Quick Start

Install the SDK, authenticate, and make your first API call in minutes.

Install

npm install @dineator/sdk

Authenticate

JWT via /api/auth/login

Query

Call any resource endpoint

Terminal
npm install @dineator/sdk
index.ts
import { Dineator } from '@dineator/sdk';

const client = new Dineator({
  apiUrl: 'https://api.dineator.com',
  // Pass your JWT token after login
  token: 'your-jwt-token',
});

Authentication

Dineator uses JWT Bearer tokens. Login returns an accessToken (short-lived) and a refreshToken. Pass Authorization: Bearer <token> on all protected requests.

MethodPath
POST/api/auth/login
POST/api/auth/register
POST/api/auth/refresh
POST/api/auth/logout
GET/api/auth/me
POST/api/auth/forgot-password
POST/api/auth/reset-password
POST/api/auth/magic-link
POST/api/auth/verify-magic-link
Authentication Flow
// POST /api/auth/login
const { data } = await client.auth.login({
  email: 'admin@myrestaurant.com',
  password: 'SecurePassword123!',
});

const token = data.accessToken;
// Store token — refresh at /api/auth/refresh using data.refreshToken
ℹ NoteAccess tokens expire in 15 minutes by default. Use the refresh token endpoint before expiry to get a new access token without re-login.

SDK Installation

The @dineator/sdk is a zero-dependency JavaScript/TypeScript client. It works in Node.js, Deno, browsers, and React Native.

npm
npm install @dineator/sdk
yarn
yarn add @dineator/sdk
CDN (Browser)
<script src='https://unpkg.com/@dineator/sdk@1.0.7/dist/dineator.min.js'></script>
Node.js ≥ 18
All modern browsers
React Native

SDK Full Reference

The complete SDK source. Copy this into your project if you prefer vendoring, or install via npm.

dineator-sdk.ts — Full Source
// Complete SDK source — also available at:
// https://github.com/dineator/js-sdk

class DineatorError extends Error {
  constructor(public status: number, public code: string, message: string) {
    super(message);
    this.name = 'DineatorError';
  }
}

class Dineator {
  private baseUrl: string;
  private token: string | null;

  constructor(opts: { apiUrl: string; token?: string }) {
    this.baseUrl = opts.apiUrl;
    this.token = opts.token ?? null;
  }

  setToken(token: string) { this.token = token; }

  private async request<T>(
    method: string, path: string, body?: unknown
  ): Promise<T> {
    const res = await fetch(`${this.baseUrl}${path}`, {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    const json = await res.json();
    if (!res.ok) throw new DineatorError(
      res.status, json.code ?? 'API_ERROR', json.message ?? 'Unknown error'
    );
    return json.data as T;
  }

  auth = {
    login: (body: { email: string; password: string }) =>
      this.request('POST', '/api/auth/login', body),
    refresh: (body: { refreshToken: string }) =>
      this.request('POST', '/api/auth/refresh', body),
    me: () => this.request('GET', '/api/auth/me'),
    logout: () => this.request('POST', '/api/auth/logout'),
    forgotPassword: (body: { email: string }) =>
      this.request('POST', '/api/auth/forgot-password', body),
  };

  menu = {
    getPublic: (slug: string) =>
      this.request('GET', `/api/menu/public/${slug}`),
    listCategories: () => this.request('GET', '/api/menu/categories'),
    createCategory: (body: object) =>
      this.request('POST', '/api/menu/categories', body),
    updateCategory: (id: string, body: object) =>
      this.request('PUT', `/api/menu/categories/${id}`, body),
    deleteCategory: (id: string) =>
      this.request('DELETE', `/api/menu/categories/${id}`),
    listItems: () => this.request('GET', '/api/menu/items'),
    createItem: (body: object) =>
      this.request('POST', '/api/menu/items', body),
    updateItem: (id: string, body: object) =>
      this.request('PUT', `/api/menu/items/${id}`, body),
    deleteItem: (id: string) =>
      this.request('DELETE', `/api/menu/items/${id}`),
  };

  orders = {
    placePublic: (slug: string, body: object) =>
      this.request('POST', `/api/orders/public/${slug}`, body),
    placeAdmin: (body: object) =>
      this.request('POST', '/api/orders/admin', body),
    list: () => this.request('GET', '/api/orders'),
    listKitchen: () => this.request('GET', '/api/orders/kitchen'),
    updateStatus: (id: string, body: object) =>
      this.request('PATCH', `/api/orders/${id}/status`, body),
    setKitchenItemReadiness: (orderId: string, itemId: string, ready: boolean) =>
      this.request('PATCH', `/api/orders/${orderId}/items/${itemId}/readiness`, { ready }),
    trackPublic: (orderId: string, secret: string) =>
      this.request('GET', `/api/orders/public/status/${orderId}?secret=${secret}`),
    appendItems: (orderId: string, body: object) =>
      this.request('PATCH', `/api/orders/${orderId}/append`, body),
    splitBill: (id: string, body: object) =>
      this.request('POST', `/api/orders/${id}/split`, body),
  };

  reservations = {
    create: (slug: string, body: object) =>
      this.request('POST', `/api/reservations/public/${slug}`, body),
    checkAvailability: (slug: string, params: object) => {
      const q = new URLSearchParams(params as Record<string,string>).toString();
      return this.request('GET', `/api/reservations/public/${slug}/availability?${q}`);
    },
    list: () => this.request('GET', '/api/reservations'),
    updateStatus: (id: string, body: object) =>
      this.request('PATCH', `/api/reservations/${id}/status`, body),
  };

  tables = {
    list: () => this.request('GET', '/api/tables'),
    create: (body: object) => this.request('POST', '/api/tables', body),
    update: (id: string, body: object) =>
      this.request('PUT', `/api/tables/${id}`, body),
    delete: (id: string) => this.request('DELETE', `/api/tables/${id}`),
  };

  analytics = {
    getDashboard: (params: { range: string }) => {
      const q = new URLSearchParams(params).toString();
      return this.request('GET', `/api/analytics/dashboard?${q}`);
    },
    export: (params: object) => {
      const q = new URLSearchParams(params as Record<string,string>).toString();
      return this.request('GET', `/api/analytics/export?${q}`);
    },
    refreshSnapshot: () =>
      this.request('POST', '/api/analytics/snapshot'),
  };

  theme = {
    get: (restaurantId: string) =>
      this.request('GET', `/api/restaurants/${restaurantId}/theme`),
    update: (restaurantId: string, body: object) =>
      this.request('PUT', `/api/restaurants/${restaurantId}/theme`, body),
    reset: (restaurantId: string) =>
      this.request('DELETE', `/api/restaurants/${restaurantId}/theme`),
  };

  promotions = {
    getActiveOffers: (restaurantId: string) =>
      this.request('GET', `/api/promotions/public/${restaurantId}/offers`),
    validateCoupon: (restaurantId: string, body: object) =>
      this.request('POST', `/api/promotions/public/${restaurantId}/validate-coupon`, body),
    listCoupons: () => this.request('GET', '/api/promotions/coupons'),
    createCoupon: (body: object) =>
      this.request('POST', '/api/promotions/coupons', body),
    updateCoupon: (id: string, body: object) =>
      this.request('PUT', `/api/promotions/coupons/${id}`, body),
    deleteCoupon: (id: string) =>
      this.request('DELETE', `/api/promotions/coupons/${id}`),
    listOffers: () => this.request('GET', '/api/promotions/offers'),
    createOffer: (body: object) =>
      this.request('POST', '/api/promotions/offers', body),
  };

  qr = {
    list: () => this.request('GET', '/api/qr'),
    generate: (body: object) => this.request('POST', '/api/qr/generate', body),
    delete: (id: string) => this.request('DELETE', `/api/qr/${id}`),
  };
}

Orders API

Place and manage orders. Public routes are called from the customer storefront; admin routes are for staff and kitchen displays.

MethodPath
POST/api/orders/public/:slug
GET/api/orders/public/status/:orderId?secret=
PATCH/api/orders/:orderId/append
GET/api/orders
GET/api/orders/kitchen
PATCH/api/orders/:orderId/items/:itemId/readiness
POST/api/orders/admin
PATCH/api/orders/:id/status
PATCH/api/orders/:id/items
POST/api/orders/:id/split
Orders Examples
// Place a public order (no auth — called from customer storefront)
const order = await client.orders.placePublic('restaurant-slug', {
  tableId: 'table-uuid',          // optional for DINE_IN
  orderType: 'DINE_IN',           // 'DINE_IN' | 'TAKEAWAY' | 'DELIVERY'
  customerName: 'Jane Doe',
  items: [
    { menuItemId: 'item-uuid', quantity: 2, notes: 'No onions' },
  ],
});

// Staff: place admin order (bypasses business-hours check)
const adminOrder = await client.orders.placeAdmin({
  tableId: 'table-uuid',
  orderType: 'DINE_IN',
  items: [{ menuItemId: 'item-uuid', quantity: 1 }],
});

// Update order status
await client.orders.updateStatus('order-uuid', {
  status: 'PREPARING',
});

// List orders (with kitchen view)
const kitchenOrders = await client.orders.listKitchen();
✓ TipOrder statuses progress: PENDING → CONFIRMED → PREPARING → READY → DELIVERED → COMPLETED. Use CANCELLED to cancel.

Reservations API

Accept and manage table reservations. Public endpoints power the customer-facing booking widget.

MethodPath
POST/api/reservations/public/:slug
GET/api/reservations/public/:slug/availability
GET/api/reservations/public/:slug/portal
GET/api/reservations
PATCH/api/reservations/:id/status
Reservations Examples
// Create a public reservation (no auth)
const reservation = await client.reservations.create('restaurant-slug', {
  guestName: 'John Doe',
  guestPhone: '+1234567890',
  guestEmail: 'john@example.com',
  partySize: 4,
  reservationDate: '2026-06-15',
  reservationTime: '19:30',
  notes: 'Window seat preferred',
});

// Check availability (no auth)
const slots = await client.reservations.checkAvailability(
  'restaurant-slug',
  { date: '2026-06-15', partySize: 4 }
);

// Admin: update reservation status
await client.reservations.updateStatus('reservation-uuid', {
  status: 'CONFIRMED', // 'CONFIRMED' | 'CANCELLED' | 'COMPLETED'
});

Tables & Floors API

Manage the physical layout of your restaurant — floors and tables. All routes require authentication.

MethodPath
GET/api/tables
POST/api/tables
PUT/api/tables/:id
DELETE/api/tables/:id
GET/api/floors
POST/api/floors
PATCH/api/floors/:id
DELETE/api/floors/:id
GET/api/qr
POST/api/qr/generate
DELETE/api/qr/:id

Analytics API

Access dashboard metrics, snapshots, and exportable reports. Requires MANAGER role or above.

MethodPath
GET/api/analytics/dashboard
POST/api/analytics/snapshot
GET/api/analytics/export
Analytics Examples
// Get dashboard analytics (Manager+)
const dashboard = await client.analytics.getDashboard({
  range: '7d', // '1d' | '7d' | '30d' | '90d'
});

// Export as CSV (Manager+)
const csvBlob = await client.analytics.export({
  range: '30d',
  format: 'csv',
});
ℹ NoteValid range values: 1d, 7d, 30d, 90d

Themes API

Query and update restaurant themes. Themes are JSON token objects applied via CSS custom properties. See Theme Docs for the full token schema.

MethodPath
GET/api/restaurants/:id/theme
PUT/api/restaurants/:id/theme
DELETE/api/restaurants/:id/theme
Theme Examples
// Get theme for a restaurant (public)
const theme = await client.theme.get('restaurant-uuid');

// Update theme (Admin+)
await client.theme.update('restaurant-uuid', {
  tokens: {
    colors: {
      primary: '#1d4ed8',
      accent: '#f97316',
    },
    layout: {
      headerStyle: 'frosted',
      borderRadius: 'lg',
    },
  },
});

// Reset to the default colours and fonts (Admin+)
await client.theme.reset('restaurant-uuid');

Promotions API

Manage discount coupons and limited-time offers. Public routes power the checkout coupon widget on the storefront.

MethodPath
GET/api/promotions/public/:restaurantId/offers
POST/api/promotions/public/:restaurantId/validate-coupon
GET/api/promotions/coupons
POST/api/promotions/coupons
PUT/api/promotions/coupons/:id
DELETE/api/promotions/coupons/:id
GET/api/promotions/offers
POST/api/promotions/offers
PUT/api/promotions/offers/:id
DELETE/api/promotions/offers/:id
Promotions Examples
// Get active offers (public)
const offers = await client.promotions.getActiveOffers('restaurant-uuid');

// Validate a coupon at checkout (public)
const coupon = await client.promotions.validateCoupon(
  'restaurant-uuid',
  { code: 'SAVE10', orderTotal: 45.00 }
);

// Admin: Create a coupon
await client.promotions.createCoupon({
  code: 'SUMMER20',
  discountType: 'PERCENTAGE',
  discountValue: 20,
  minOrderAmount: 30,
  maxUsages: 100,
  expiresAt: '2026-08-31T23:59:59Z',
});

Error Handling

All API responses follow a standard envelope. On error, the SDK throws a DineatorError.

StatusCodeMeaning
400VALIDATION_ERRORRequest body failed schema validation
401UNAUTHORIZEDMissing or expired JWT token
403FORBIDDENValid token but insufficient role/permission
404NOT_FOUNDResource does not exist
409CONFLICTUnique constraint violation (e.g. duplicate slug)
422UNPROCESSABLEBusiness rule violation (e.g. restaurant closed)
429RATE_LIMITEDToo many requests — back off and retry
500INTERNAL_ERRORServer-side error — contact support
Error Handling
// The SDK throws DineatorError on non-2xx responses
import { DineatorError } from '@dineator/sdk';

try {
  await client.menu.createItem({ /* ... */ });
} catch (err) {
  if (err instanceof DineatorError) {
    console.log(err.status);   // HTTP status code, e.g. 422
    console.log(err.code);     // API error code, e.g. "VALIDATION_ERROR"
    console.log(err.message);  // Human-readable message
  }
}

Rate Limits

Rate limits protect the API from abuse. Limits are applied per IP on public endpoints and per user on authenticated endpoints.

Route GroupLimitWindow
POST /api/auth/login10 requests15 minutes
POST /api/auth/register5 requests1 hour
POST /api/auth/magic-link5 requests15 minutes
POST /api/auth/forgot-password5 requests15 minutes
All other /api/* routes200 requests15 minutes
⚠ WarningWhen rate limited, the API returns 429 RATE_LIMITED. Implement exponential backoff — wait at least 60 seconds before retrying.

Ready to build?

Explore the interactive Swagger UI for live API testing, or grab the full SDK source from GitHub.