Hjelp / API

Headless API

Bygg billettkjøp på egen nettside. Brumio håndterer lager, ordre og betaling — kjøperen omdirigeres til SagaPay, og billetter sendes på e-post som vanlig.

Slik starter du

  1. Logg inn og gå til Innstillinger.
  2. Slå på Headless API og opprett en hemmelig nøkkel (bare eieren).
  3. Kall API-et fra serveren deres — aldri eksponer nøkkelen i nettleseren.

Autentisering

Send nøkkelen som Authorization: Bearer br_sk_… eller header X-Brumio-Api-Key. Nøkkelen må være aktiv og API-et må være slått på for arrangøren.

Testnøkler starter med br_sk_test_, live med br_sk_live_ (følger testmodus på kontoen).

Endepunkter

GET https://www.brumio.no/api/v1/integr/events/{orgSlug}/{eventSlug}

Publisert arrangement med billettyper, salgsstatus og gebyrregel — samme data som på Brumio-siden deres. Feltet seatingEnabled er true når kjøp krever valgte seter.

GET https://www.brumio.no/api/v1/integr/events/{orgSlug}/{eventSlug}/seats

Setekart for arrangementet: sections (seksjon, billettype, pris) og seats (rad, nummer, seksjon, status: available eller taken). Oppdater kartet jevnlig (f.eks. hvert 30. sekund) mens kjøperen velger — solgte og holdte seter er taken.

404 når arrangementet ikke har setekart. Samme JSON som offentlig GET /api/v1/events/{eventId}/seats, men autentisert og med CORS for tillatte domener.

POST https://www.brumio.no/api/v1/integr/promo/check

Body: { "eventId": "…", "code": "RABATT" }. Sjekk rabattkode før checkout.

POST https://www.brumio.no/api/v1/integr/checkout

Oppretter ordre og returnerer payment.embed for self-hosted checkout med SagaPay Online SDK (sdkUrl, publicKey, providerOrderId, nonce). Last sdkUrl og kall SurfboardOnlineSDK.initialiseOnlineSDK({ publicKey, orderId: providerOrderId, nonce }). SagaPay godtar bare domenet som er godkjent for butikken (www.brumio.no). Krever header Idempotency-Key (16–100 tegn) — samme nøkkel gir samme svar ved retry.

Body følger samme skjema som offentlig checkout, pluss valgfritt successUrl og cancelUrl (må ligge på tillatte domener i API-innstillinger). Etter betaling sendes kjøperen til successUrl?orderId=…&t=… i stedet for Brumio-kvittering.

Redirect kjøperen til betaling: bruk paymentPageUrl fra responsen (eller payment.redirectUrl når den finnes og peker til SagaPay). Ikke send kjøperen til brumioReceiptUrl eller payment.embed.receiptUrl før betaling er fullført — det er kvittering/retur-URL etter betaling. Gratis ordre (totalOre: 0) er allerede betalt; da kan dere bruke brumioReceiptUrl.

Setekart: når seatingEnabled er true, må hver linje ha seatIds (én UUID per sete; quantity må matche). Maks 6 seter per ordre. Uten setekart sendes bare ticketTypeId og quantity.

GET https://www.brumio.no/api/v1/integr/orders/{orderId}

Status, kjøper, arrangement og receipt (linjer, rabatt, gebyr, mva) når ordren er betalt eller venter. Bruk dette på deres takk-side (server-til-server med API-nøkkel) for å vise kvittering på egen nettside. ticketPageUrl peker til billettene når de er klare.

Betaling og sikkerhet

Dere lagrer eller sender aldri kortnummer. Enklest flyt: etter POST /checkout, send kjøperen til paymentPageUrl på brumio.no — der fullføres SagaPay-betalingen. Alternativt kan dere bygge egen side med payment.embed, men SDK-et må da kjøre på domenet SagaPay har godkjent (typisk www.brumio.no). Med successUrl kommer kjøperen tilbake til siden deres etter betaling med orderId og t; hent kvitteringsdata fra ordre-endepunktet. Billett sendes på e-post som vanlig.

Hvis dere kaller API-et fra nettleseren (ikke anbefalt), må nettstedets origin stå under tillatte domener i innstillingene. Server-til-server-kall trenger ikke CORS-oppsett.

Eksempel (Node, uten setekart)

const res = await fetch('https://www.brumio.no/api/v1/integr/checkout', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + process.env.BRUMIO_SECRET_KEY,
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    eventId: '…',
    buyer: {
      name: 'Ola Nordmann',
      email: 'ola@example.com',
      phone: '900 00 000',
      addressLine1: 'Storgata 1',
      postalCode: '0150',
      city: 'Oslo',
    },
    lines: [{ ticketTypeId: '…', quantity: 2 }],
    acceptTerms: true,
  }),
});
const { paymentPageUrl } = await res.json();
window.location.href = paymentPageUrl; // betaling på brumio.no

Eksempel (Node, setekart)

Hent arrangement, deretter setekart. Bygg deres eget kart (eller liste) ut fra seats — vis bare seter med status: "available". Send valgte id-verdier i checkout.

const headers = {
  Authorization: 'Bearer ' + process.env.BRUMIO_SECRET_KEY,
};

const orgSlug = 'mitt-selskap';
const eventSlug = 'sommerkonsert';

const eventRes = await fetch(
  `https://www.brumio.no/api/v1/integr/events/${orgSlug}/${eventSlug}`,
  { headers },
);
const { event } = await eventRes.json();
const { id: eventId, seatingEnabled } = event;

if (!seatingEnabled) {
  // Vanlig checkout med ticketTypeId + quantity (se eksempel over)
  throw new Error('Forventet setekart');
}

const seatsRes = await fetch(
  `https://www.brumio.no/api/v1/integr/events/${orgSlug}/${eventSlug}/seats`,
  { headers },
);
const { sections, seats } = await seatsRes.json();

const pick = seats.filter((s) => s.status === 'available').slice(0, 2);
if (pick.length < 2) throw new Error('Ikke nok ledige seter');

// Grupper per billettype (samme type kan ha flere seter på én linje)
const byType = new Map();
for (const s of pick) {
  const cur = byType.get(s.ticketTypeId) ?? { ticketTypeId: s.ticketTypeId, seatIds: [] };
  cur.seatIds.push(s.id);
  byType.set(s.ticketTypeId, cur);
}
const lines = [...byType.values()].map((l) => ({
  ticketTypeId: l.ticketTypeId,
  quantity: l.seatIds.length,
  seatIds: l.seatIds,
}));

const checkoutRes = await fetch('https://www.brumio.no/api/v1/integr/checkout', {
  method: 'POST',
  headers: {
    ...headers,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    eventId,
    buyer: { name: 'Ola Nordmann', email: 'ola@example.com', phone: '900 00 000' },
    lines,
    acceptTerms: true,
    successUrl: 'https://example.com/takk',
  }),
});
const { payment } = await checkoutRes.json();
// sections[0].label, pick[0].row + pick[0].number — vis valgte plasser i UI