Best practices

Caching, error handling, pagination, rate limits, logging - what distinguishes a production-ready integration from one you rebuild three times.

The go~mus API is open and you can build almost anything with it. To save you from the same traps others fell into, a compact list of practices that have proven themselves.

Caching is mandatory, not a recommendation

Poll master data daily and cache it. Availability live but not per end-customer request. Direct live traffic on master data is rejected and will not get to production. More under Data freshness: pull, not push.

Concrete cache layers that have worked:

  • HTTP cache with correct max-age headers at the reverse proxy (Varnish, Cloudflare, nginx)
  • Application cache (Redis, Memcached) with explicit invalidation in your daily sync job
  • ETag or Last-Modified based, if you want to use conditional GETs

Handle errors gracefully

Order creation can fail at any time, even after a successful reservation: capacity was reduced, another channel sold faster, price calculation diverges. Be ready.

  • 4xx from go~mus: read the body, surface the error to the customer, step back if needed
  • 5xx from go~mus: retry with exponential backoff, max 3 attempts, then a friendly error message
  • Reservation expired: redirect to selection, recheck availability

An order cannot be confirmed retroactively if it was never created. Never.

Pagination

Every index endpoint paginates. Default 25, may vary per instance. Read meta.total_count, meta.page, meta.per_page and page through cleanly.

async function fetchAllTickets(token) {
  const all = [];
  let page = 1;
  while (true) {
    const res = await fetch(`https://demo.gomus.de/api/v4/tickets?page=${page}`, {
      headers: { Authorization: `Bearer ${token}` }
    });
    const data = await res.json();
    all.push(...data.tickets);
    if (page >= Math.ceil(data.meta.total_count / data.meta.per_page)) break;
    page++;
  }
  return all;
}

Multi-locale in one round-trip

If you need DE and EN, do not chain two calls. One call with ?locale=de returns both.

Keep tokens server-side

Never put tokens in browser bundles, mobile app bundles or frontend JS. Tokens live on your server, API calls go through your proxy.

Compute the total yourself, server validates

When creating an order you send total as the sum of all item prices. go~mus recomputes its own number and only creates the order if your total matches. The double computation guards against price manipulation and misconfigured discounts.

That means: you compute the price on your side from the price information that go~mus exposes in the relevant master data endpoints.

Where prices come from per product type:

  • Tickets: price_cents, discount, vat_pct, tax_included straight on the ticket object - GET /api/v4/tickets or GET /api/v4/tickets/:id. The /capacity endpoint exposes availability only, not prices.
  • Events (dates): prices array on the date detail under GET /api/v4/events/:event_id/dates/:id. Two types: default price and scale price (e.g. "regular", "reduced", with scale_price_id and title). A single date can carry several options.
  • Tours: dedicated price endpoint GET /api/v4/tours/:id/prices. Default, scale and fee-schedule based prices plus surcharges (foreign language, Sunday etc.). On each price object, group (per group vs. per participant) and optional (required vs. choice) describe the behaviour. Prices depend on date, time, participants, customer category and language.

If the server rejects your total: refresh master data (invalidate cache), recompute, retry. Often it is a stale price or a changed discount configuration.

foreign_id for customer mapping

If you operate your own CRM, attach a foreign_id to customer records. On next update reference via foreign_id instead of the go~mus ID. Saves the mapping table. More under foreign_id pattern.

Logging

Log every API call: timestamp, endpoint, HTTP status, response time, token identifier (not the token itself). On support tickets you will need this to reproduce.

What not to log: tokens, customer PII, orders with cleartext data. Logs are audit material, not a data archive.

Idempotency and retries

On a network error you do not know whether the order went through. Things to know:

  • Reservation: not idempotent. The same call creates a second reservation. Reservations cannot be queried via the API. On timeout, do not try to find the previous one. Let it expire after 5 minutes and start fresh with a new capacity check and reservation.
  • Create order: not idempotent, but go~mus auto-cancels orders that are not finalized within a short window. On timeout simply restart the flow, half-created duplicates fall away on their own.
  • Finalize: idempotent, safe to retry.

Example code repos

What will break

Plans that go wrong in practice:

  • "We cache availability for 5 minutes, that will be enough." → Sold-out dates get sold again, customers are upset.
  • "We compute prices ourselves from list prices." → Discounts, promos, dynamic pricing trigger the server validation and you ship a bug.
  • "We fetch tokens via AJAX in the browser." → Token in the browser equals public token. Immediate rotation needed.

When you are stuck

Email the concrete API call, timestamp, expected vs. actual behaviour to support@giantmonkey.de. The most common cause of trouble is an old or wrong token. Helpful: include the last four characters of the token you used, or the full token - we rotate it afterwards. The more precise your question, the faster the answer.