Reference

Website widget

The booking form lives on the restaurant's own website. The browser never holds an API key: it talks to a thin proxy on your server, and the proxy adds the key and forwards to the public API. The live demo runs exactly this pattern.

The proxy pattern

Flow
Browser ── POST /api/website/reservations ──▶ Your server ── POST /api/v1/... + API key ──▶ Vardvik

Three rules keep it safe:

  • The API key lives in a server-side environment variable, never in markup or bundles.
  • The proxy fixes restaurant and location ids server-side; the browser cannot choose them.
  • The proxy generates the idempotency key per submission, so double-clicks cannot double-book.

Next.js proxy route

app/api/website/reservations/route.ts
export async function POST(request: Request) {
  const body = await request.json();

  const response = await fetch(
    `${process.env.VARDVIK_BASE_URL}/api/v1/restaurants/${process.env.VARDVIK_RESTAURANT_ID}/reservations`,
    {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.VARDVIK_API_KEY}`,
        "content-type": "application/json",
        "idempotency-key": crypto.randomUUID()
      },
      body: JSON.stringify({
        locationId: process.env.VARDVIK_LOCATION_ID,
        startTime: body.startTime,
        partySize: body.partySize,
        guest: body.guest,
        specialRequests: body.specialRequests
      })
    }
  );

  return Response.json(await response.json(), { status: response.status });
}

Availability lookup

app/api/website/availability/route.ts
export async function GET(request: Request) {
  const url = new URL(request.url);
  const upstream = new URL(
    `/api/v1/restaurants/${process.env.VARDVIK_RESTAURANT_ID}/availability`,
    process.env.VARDVIK_BASE_URL
  );
  upstream.searchParams.set("locationId", process.env.VARDVIK_LOCATION_ID!);
  upstream.searchParams.set("date", url.searchParams.get("date") ?? "");
  upstream.searchParams.set("partySize", url.searchParams.get("partySize") ?? "2");

  const response = await fetch(upstream, {
    headers: { authorization: `Bearer ${process.env.VARDVIK_API_KEY}` },
    cache: "no-store"
  });

  return Response.json(await response.json(), { status: response.status });
}

The form itself

Any form works: load slots from your availability proxy, let the guest pick one, post the booking, show the publicReference as the confirmation code. Working examples ship in the repository under examples/: a plain HTML form, a React component, and the Next.js proxy above.

Minimal client call
const response = await fetch("/api/website/reservations", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    startTime: selectedSlot,
    partySize: 4,
    guest: { firstName, lastName, email }
  })
});

const { reservation, error } = await response.json();
if (reservation) {
  show(`Confirmed. Your reference: ${reservation.publicReference}`);
}

Guest self-registration

Restaurants can also link guests to a hosted registration page with a signed token, so profiles and consents are recorded without any API work on your side. See guest registration.