Developer reference

Partner API reference (v1)

The Goboblo Partner API is a server-to-server API. Your backend calls us with a secret key to read open times, create and cancel bookings, and read the menu for the locations your key is allowed to see. Goboblo owns the booking itself — the table plan, the double-booking protection and the guest confirmation email.

Base URL
https://api.goboblo.com/v1/partner-api
Auth
Bearer key (gbp_…), server-side only
Format
JSON over HTTPS

Get a free sandbox key in about 30 seconds — see First booking in 5 minutes. Prefer a machine-readable spec? Download the OpenAPI file.

Get a sandbox key

Verify your email and get a test key in about 30 seconds — no signup, no card. The key is locked to our shared demo restaurant, has all four scopes, and expires after 30 days.

No signup, no card. We verify your email, then show the key once. It's locked to our shared demo restaurant and expires after 30 days.

After you submit, we email you a link. Opening it takes you to the verification page, where your key is shown once — copy it there and then save it somewhere safe. Building for production instead? Real keys are issued manually — see Getting a key.

First booking in 5 minutes

Four steps, all runnable against our shared demo restaurant (00000000-0000-0000-0000-000000000010). Copy, paste, get a green response.

  1. 1. Get a sandbox key

    Use the form above — enter your email and system name, open the link we email you, and copy the key shown once on the verification page. No signup, no card. Swap gbp_your_sandbox_key below for your key.

  2. 2. Check availability

    Read open times for a party of two (use any upcoming date):

    curl "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/availability?date=2026-07-20&partySize=2" \
      -H "Authorization: Bearer gbp_your_sandbox_key"

    Response 200:

    {
      "locationId": "00000000-0000-0000-0000-000000000010",
      "date": "2026-07-20",
      "timezone": "Europe/Stockholm",
      "slots": [
        { "slot": "2026-07-20T09:00:00.000Z", "available": true, "remainingCovers": 100 },
        { "slot": "2026-07-20T09:15:00.000Z", "available": true, "remainingCovers": 100 }
      ]
    }

    Slot times are UTC. The demo restaurant is open 11:00–22:00 Europe/Stockholm, so the first slot is 09:00Z in summer.

  3. 3. Create a booking

    Pick a startsAt from an available slot and send an idempotencyKey so a retry never double-books. Make the key your own unique string — the sandbox restaurant is shared, and a key another developer already used replays their booking instead of creating yours:

    curl -X POST "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/bookings" \
      -H "Authorization: Bearer gbp_your_sandbox_key" \
      -H "Content-Type: application/json" \
      -d '{
        "startsAt": "2026-07-20T17:00:00.000Z",
        "partySize": 2,
        "name": "Test Guest",
        "email": "you@example.com",
        "idempotencyKey": "yourname-first-booking-1"
      }'

    Response 201:

    { "id": "b1a2…", "startsAt": "2026-07-20T17:00:00.000Z", "status": "confirmed", "partnerBookingRef": null }

    Keep the returned id for the next step.

  4. 4. Cancel it

    Replace BOOKING_ID with the id from step 3:

    curl -X POST "https://api.goboblo.com/v1/partner-api/bookings/BOOKING_ID/cancel" \
      -H "Authorization: Bearer gbp_your_sandbox_key"

    Response 200:

    { "ok": true }

That is the full round trip. Every endpoint has curl, JavaScript and PHP examples below, and the sandbox rules are in Sandbox / demo restaurant.

Overview

  • Server-to-server. Call us from your backend with a secret key — never from a browser or app.
  • You read open times, create bookings, read booking status, cancel, and read the menu for the locations your key may see.
  • Goboblo owns the booking: table plan, double-booking protection, guest confirmation email.

Glossary

TermMeaning
locationIdGoboblo's location UUID. You reference a restaurant with this. Issued to you by us.
API key (gbp_…)Your secret. Shown in cleartext once when we create it — store it safely. We keep only a hash.
ScopeWhat the key may do (see the table below).
partnerBookingRefYour own booking id. If you send it, we store it and mirror it back (cross-reference).
idempotencyKeyYour retry key on create. Same key + location → the same booking back, never a duplicate.
Webhook endpointYour URL where we POST booking.* events, with its own signing secret (whsec_…).

Authentication

Send the key on every request, one of two ways:

Authorization: Bearer gbp_your_key_here

or

x-goboblo-key: gbp_your_key_here
  • The key is issued by us. Never put it in the frontend.
  • Missing or invalid key → 401. Key without the right scope → 403.

Scopes

ScopeGrants access to
availability:readGET …/availability
bookings:readGET …/bookings/:id
bookings:writePOST …/bookings, POST …/bookings/:id/cancel
menu:readGET …/menu

A key can also be locked to specific locations (allowed_location_ids). Call a location outside the list → 403 (availability/booking) or 404 (status/cancel — we do not reveal that the booking exists).

Every key is issued with an explicit location list. A key without a list can, for safety reasons, create bookings but not read or cancel them (403 "nyckeln saknar lokal-allowlist" on GET / cancel) — if your key is missing locations you need, get in touch and we update the list.

Rate limits

  • Per key: default 120 requests/minute (can be raised per key — ask us). Over the limit → 429.
  • Build in backoff on 429.
  • The limit is counted per authenticated key — a request with an invalid key gets 401 straight away and never reaches the counter.

Errors

Standard JSON (NestJS). The message string is human-readable and may be localized — branch on statusCode:

{ "statusCode": 403, "message": "saknar scope: bookings:write", "error": "Forbidden" }
CodeMeans
400Invalid input (e.g. wrong date format, invalid partySize).
401Missing or invalid key.
403Key lacks the scope, or may not see the location.
404Booking or location not found (or not visible to the key).
409Conflict — the time/table was just taken (concurrency protection), or the location is closed.
429Per-key rate limit exceeded.
503The Partner API is not enabled yet (operational state) — try again later.

Endpoints

1. Availability

GET /v1/partner-api/locations/{locationId}/availability?date=YYYY-MM-DD&partySize=N

Scope: availability:read. Designed for sub-1-second responses. Micro-cached about 8 seconds per locationId|date|partySize.

Response 200:

{
  "locationId": "8f3c…",
  "date": "2026-07-20",
  "timezone": "Europe/Stockholm",
  "slots": [
    { "slot": "2026-07-20T17:00:00.000Z", "available": true,  "remainingCovers": 12 },
    { "slot": "2026-07-20T17:30:00.000Z", "available": false, "remainingCovers": 0 }
  ]
}

If the location is closed that day → slots: [].

Code examples

curl "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/availability?date=2026-07-20&partySize=2" \
  -H "Authorization: Bearer gbp_your_sandbox_key"
const res = await fetch(
  "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/availability?date=2026-07-20&partySize=2",
  { headers: { Authorization: "Bearer gbp_your_sandbox_key" } }
);
const data = await res.json();
<?php
$ch = curl_init("https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/availability?date=2026-07-20&partySize=2");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer gbp_your_sandbox_key"]);
$data = json_decode(curl_exec($ch), true);

1b. Availability — batch (date range × party sizes)

POST /v1/partner-api/locations/{locationId}/availability/batch

Scope: availability:read (same as the single call). Built for Reserve with Google-style batch lookups and feed generation: a whole date range × several party sizes in one call, instead of hundreds of single calls. Micro-cached about 8 seconds per locationId|startDate|endDate|partySizes; identical concurrent calls share one fetch (single-flight).

Body:

{
  "startDate": "2026-07-20",
  "endDate": "2026-08-18",
  "partySizes": [2, 4, 6]
}

Limits: max 92 days per call (endDatestartDate), 1–12 party sizes, each size 1–50. Outside the limits → 400. Duplicates in partySizes are merged.

Response 200

Grouped per (day, party size), sorted by day → size → time:

{
  "locationId": "8f3c…",
  "startDate": "2026-07-20",
  "endDate": "2026-08-18",
  "timezone": "Europe/Stockholm",
  "days": [
    { "date": "2026-07-20", "partySize": 2, "slots": [
      { "slot": "2026-07-20T15:00:00.000Z", "available": true,  "remainingCovers": 12 },
      { "slot": "2026-07-20T15:30:00.000Z", "available": false, "remainingCovers": 0 }
    ] },
    { "date": "2026-07-20", "partySize": 4, "slots": [
      { "slot": "2026-07-20T15:00:00.000Z", "available": true, "remainingCovers": 12 }
    ] },
    { "date": "2026-07-21", "partySize": 2, "slots": [ … ] }
  ]
}
  • Responses are identical to the single call for each (day, size) — same slots, same available, same remainingCovers. You can mix single and batch calls freely.
  • Days missing from days have no availability: the location is closed that day, or no service period allows that party size. There is never an entry with an empty slots list — absence means nothing is bookable.
  • One entry per partySize per day maps cleanly to feed formats that need one availability row per party size and time (e.g. Reserve with Google).

Code examples

curl -X POST "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/availability/batch" \
  -H "Authorization: Bearer gbp_your_sandbox_key" \
  -H "Content-Type: application/json" \
  -d '{ "startDate": "2026-07-20", "endDate": "2026-07-27", "partySizes": [2, 4] }'
const res = await fetch(
  "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/availability/batch",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer gbp_your_sandbox_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ startDate: "2026-07-20", endDate: "2026-07-27", partySizes: [2, 4] }),
  }
);
const data = await res.json();
<?php
$ch = curl_init("https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/availability/batch");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  "Authorization: Bearer gbp_your_sandbox_key",
  "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  "startDate" => "2026-07-20", "endDate" => "2026-07-27", "partySizes" => [2, 4],
]));
$data = json_decode(curl_exec($ch), true);

2. Create booking

POST /v1/partner-api/locations/{locationId}/bookings

Scope: bookings:write.

Body:

{
  "startsAt": "2026-07-20T17:00:00.000Z",
  "partySize": 4,
  "name": "Anna Andersson",
  "email": "anna@example.com",
  "phone": "+46701234567",
  "notes": "Window table if possible",
  "partnerBookingRef": "rwg-abc-123",
  "idempotencyKey": "your-unique-retry-key-123",
  "lang": "sv"
}

phone, notes, partnerBookingRef, idempotencyKey and lang are optional. langsv|en|no|da (sets the guest's confirmation/reminder language).

Response 201:

{ "id": "b1a2…", "startsAt": "2026-07-20T17:00:00.000Z", "status": "confirmed", "partnerBookingRef": "rwg-abc-123" }
  • Goes through the exact same booking engine as the guest widget (advisory lock + GiST) → concurrency-safe. Two concurrent bookings for the last table → exactly one 201, one 409.
  • The booking gets source = "partner" and channel = "partner:<your-slug>" (visible in the restaurant's channel stats). The guest gets a confirmation email in their language as usual.
  • We leak no manage token to you; the guest self-serves via the email link. You cancel via the booking id below.

Idempotency (idempotencyKey) — protection against double booking on retry

Send your own unique key (max 120 characters, e.g. a UUID or Google's idempotency_token) in idempotencyKey. Then:

  • Replay: the same key against the same location again → you get the same booking back (200 content in a 201 response, the exact same shape) — a duplicate is never created. This holds even if two retries arrive at the same time: exactly one booking is created, both calls get it.
  • Got a timeout or network error on a create? Resend the exact same request with the same key — that is the whole point. Change the key only when you deliberately want to create a NEW booking.
  • The key is unique per location — different locations can use the same key without conflict.
  • idempotencyKeypartnerBookingRef: the ref is a soft cross-reference for tracking (may be reused, mirrored in status/webhooks); the key is a hard uniqueness guarantee that governs the create flow itself.
  • Leave the key out and everything works as before — but then a retry is a new booking. Strongly recommended for anyone building automatic retry.

Replay example (second call with the same key):

{ "id": "b1a2…", "startsAt": "2026-07-20T17:00:00.000Z", "status": "confirmed", "partnerBookingRef": "rwg-abc-123" }

Same id as the first response. If the booking has since changed, status reflects it (e.g. cancelled).

Code examples

curl -X POST "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/bookings" \
  -H "Authorization: Bearer gbp_your_sandbox_key" \
  -H "Content-Type: application/json" \
  -d '{
    "startsAt": "2026-07-20T17:00:00.000Z",
    "partySize": 2,
    "name": "Test Guest",
    "email": "you@example.com",
    "idempotencyKey": "yourname-unique-key-1"
  }'
const res = await fetch(
  "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/bookings",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer gbp_your_sandbox_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      startsAt: "2026-07-20T17:00:00.000Z",
      partySize: 2,
      name: "Test Guest",
      email: "you@example.com",
      idempotencyKey: "yourname-unique-key-1",
    }),
  }
);
const booking = await res.json();
<?php
$ch = curl_init("https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/bookings");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  "Authorization: Bearer gbp_your_sandbox_key",
  "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  "startsAt" => "2026-07-20T17:00:00.000Z",
  "partySize" => 2,
  "name" => "Test Guest",
  "email" => "you@example.com",
  "idempotencyKey" => "yourname-unique-key-1",
]));
$booking = json_decode(curl_exec($ch), true);

3. Read booking status

GET /v1/partner-api/bookings/{bookingId}

Scope: bookings:read.

Response 200:

{
  "id": "b1a2…",
  "locationId": "8f3c…",
  "startsAt": "2026-07-20T17:00:00.000Z",
  "partySize": 4,
  "status": "confirmed",
  "source": "partner",
  "channel": "partner:reserve-with-google",
  "partnerBookingRef": "rwg-abc-123",
  "createdAt": "2026-07-09T10:00:00.000Z"
}

No extra guest PII beyond what you sent in yourself (see GDPR below).

Code examples

curl "https://api.goboblo.com/v1/partner-api/bookings/BOOKING_ID" \
  -H "Authorization: Bearer gbp_your_sandbox_key"
const res = await fetch(
  "https://api.goboblo.com/v1/partner-api/bookings/BOOKING_ID",
  { headers: { Authorization: "Bearer gbp_your_sandbox_key" } }
);
const booking = await res.json();
<?php
$ch = curl_init("https://api.goboblo.com/v1/partner-api/bookings/BOOKING_ID");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer gbp_your_sandbox_key"]);
$booking = json_decode(curl_exec($ch), true);

4. Cancel

POST /v1/partner-api/bookings/{bookingId}/cancel

Scope: bookings:write. Idempotent — already cancelled → { "ok": true }.

Response 200:

{ "ok": true }

Bookings in a final state that cannot be cancelled (e.g. already completed or marked no-show) → 400 "bokningen går inte att avboka".

Code examples

curl -X POST "https://api.goboblo.com/v1/partner-api/bookings/BOOKING_ID/cancel" \
  -H "Authorization: Bearer gbp_your_sandbox_key"
const res = await fetch(
  "https://api.goboblo.com/v1/partner-api/bookings/BOOKING_ID/cancel",
  { method: "POST", headers: { Authorization: "Bearer gbp_your_sandbox_key" } }
);
const data = await res.json(); // { ok: true }
<?php
$ch = curl_init("https://api.goboblo.com/v1/partner-api/bookings/BOOKING_ID/cancel");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer gbp_your_sandbox_key"]);
$data = json_decode(curl_exec($ch), true); // ["ok" => true]

5. Read menu

GET /v1/partner-api/locations/{locationId}/menu

Scope: menu:read. Returns the restaurant's menu in the format below.

Response 200:

{
  "locationName": "Krogen",
  "currency": "SEK",
  "categories": [
    { "id": "c1", "name": "Förrätter", "description": null,
      "items": [ { "id": "i1", "name": "Toast Skagen", "description": "…", "price_ore": 16500, "tags": [] } ] }
  ]
}

Code examples

curl "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/menu" \
  -H "Authorization: Bearer gbp_your_sandbox_key"
const res = await fetch(
  "https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/menu",
  { headers: { Authorization: "Bearer gbp_your_sandbox_key" } }
);
const menu = await res.json();
<?php
$ch = curl_init("https://api.goboblo.com/v1/partner-api/locations/00000000-0000-0000-0000-000000000010/menu");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer gbp_your_sandbox_key"]);
$menu = json_decode(curl_exec($ch), true);

Sandbox / demo restaurant

Every sandbox key is locked to one shared demo restaurant, so you can build and test without touching a real venue. It has all four scopes, a rate limit of 60 requests/minute, and expires after 30 days. You can hold up to three active sandbox keys per email.

Location id
00000000-0000-0000-0000-000000000010
Timezone
Europe/Stockholm
Open
Every day 11:00–22:00 (responses are UTC)
Slots
Every 15 minutes

Tables at the demo restaurant:

TableSeatsNote
T11–2
T22–4
T34–6Window — the only table that seats 6
T41–2Bar

Because the demo restaurant is shared, testers compete for the same tables — book a party of six on the same slot twice to see the 409 concurrency guard in action.

Auto-cleanup. Bookings on the demo restaurant older than 24 hours are cancelled nightly, so the calendar stays open every morning. Sandbox keys expire after 30 days — request a new one with the same email to keep going.

Booking sources / attribution

Every partner booking is marked with source = "partner" and channel = "partner:<slug>" (e.g. partner:reserve-with-google). If you send partnerBookingRef, it is mirrored back in the status response and in the webhook payload.

Webhooks (outgoing events to you)

If you want to know when bookings change, we register a webhook endpoint for you (URL + a signing secret whsec_… shown once). We then POST JSON to your URL.

Event types

We sendWhen
booking.createdA booking was created.
booking.updatedA booking was moved or changed.
booking.cancelledA booking was cancelled.

Your endpoint can subscribe to a subset, and can be locked to specific locations.

Payload

{
  "id": "b1a2…:booking.created:1752000000",
  "type": "booking.created",
  "created": 1752000000,
  "data": {
    "bookingId": "b1a2…",
    "locationId": "8f3c…",
    "status": "confirmed",
    "startsAt": "2026-07-20T17:00:00.000Z",
    "endsAt": "2026-07-20T19:00:00.000Z",
    "partySize": 4,
    "source": "partner",
    "channel": "partner:reserve-with-google",
    "partnerBookingRef": "rwg-abc-123",
    "guest": { "name": "Anna Andersson", "email": "anna@example.com", "phone": "+46701234567" }
  }
}

Headers

HeaderContent
x-goboblo-signaturet=<unix>,v1=<hex> — see signature verification below.
x-goboblo-event-idSame as id in the payload — use as the idempotency key.
content-typeapplication/json

Signature verification (Stripe-style)

Sign the raw body (not a re-serialized version) together with the timestamp: v1 = HMAC_SHA256(secret, "<t>.<rawBody>") in hex, where secret is your endpoint's whsec_….

Node.js example:

const crypto = require("crypto");

function verify(rawBody, header, secret) {
  // header = "t=1752000000,v1=abcd…"
  const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  // Constant-time comparison:
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
  // REQUIRED replay protection: reject deliveries older than 5 minutes — without
  // the tolerance check, an intercepted delivery can be replayed with a valid signature.
  return Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
}

Respond 2xx quickly (within about 5 s). Any other response or a timeout counts as a failure — we retry the delivery.

Replay protection, summarized: (1) verify the signature on the raw body, (2) reject |now − t| > 300 s, (3) de-duplicate on x-goboblo-event-id (the same event may be delivered more than once — the second time should be a no-op with 2xx).

Retry + idempotency

  • On failure we make about 5 attempts with growing delay (10 s, 1 min, 5 min, 30 min).
  • The same event carries the same x-goboblo-event-id on every attempt — de-duplicate on it.
  • If your endpoint keeps failing (about 15 consecutive failed events) we disable it; we re-enable it manually once it works again.

GDPR / roles

The restaurant is the data controller for guest data. Goboblo is a data processor; you as a partner are a channel/processor. The guest block in the webhook is opt-in per endpoint (off by default — then "guest": null in the payload) and is turned on only when a processing agreement exists and you need the guest data to mirror the booking (e.g. Reserve with Google sync) — handle it accordingly and delete it per your agreement.

Test checklist

  • Request without / with a wrong key → 401; a valid key without the right scope → 403.
  • Key locked to locations → 403/404 against a location outside the list.
  • GET availability responds in under 1 s and matches the widget.
  • POST availability/batch gives the same slots as the single calls for each (day, size); 93 days or 13 sizes → 400.
  • POST bookings creates a booking visible to the restaurant with channel partner:<slug>.
  • GET/cancel on a booking your key did NOT create (another channel) → 404 — you only see your own.
  • A webhook delivery older than 5 min is rejected by your verification; a duplicated x-goboblo-event-id is a no-op.
  • Two concurrent bookings for the last table → exactly one 201, one 409.
  • POST bookings twice with the same idempotencyKey → the same booking id both times, only ONE booking at the restaurant.
  • GET bookings/:id mirrors partnerBookingRef and the right status.
  • POST cancel is idempotent (second call → { "ok": true }).
  • GET menu returns the menu in the format above.
  • You can recompute v1 with your secret over "<t>.<rawBody>" and get the same hex.
  • You de-duplicate on x-goboblo-event-id on retry.
  • On 429: you back off and try again.

OpenAPI spec

A machine-readable OpenAPI 3 description of every endpoint, request, response and error is published alongside this page. Load it into Postman, Insomnia or your own code generator.

Download the OpenAPI spec (YAML)

The spec is kept in sync with this reference and the underlying v1 contract.

Changelog & versioning

DateVersionChange
2026-07v1Initial release: availability (single + batch), create/read/cancel bookings, read menu, and signed webhooks.

Versioning policy

  • v1 is stable. Additive changes — new optional fields or new endpoints — ship without a version bump, so build defensively and ignore fields you do not use.
  • Breaking changes ship as a new version (/v2); v1 stays supported and running in parallel, with notice before anything is retired.

Support

Questions or something not working? Email help@goboblo.com — we respond within one business day.

Getting a key

Sandbox: self-serve in about 30 seconds with the form above — a test key locked to the demo restaurant. Manage your keys any time in the developer portal.

Production: real keys are issued manually by us, one per partner, with an explicit location list (this keeps the data-processing agreement and per-webhook PII opt-in in place). Apply for production access and we respond within one business day: start on Build with Goboblo, email help@goboblo.com, or get in touch — tell us what your system should do and we set up the key and any webhook endpoint together with you.