Grofomo API
Browse the docs

Quickstart

Your first request, and a working lineup page in about ten minutes.

Examples use a fictional festival, summer-fest. Sign in at events.grofomo.com and they switch to your own events.

You need two things before you start: an event slug and a publishable key. Both come from the organiser — if you are the organiser, see For organisers.

The slug is the last part of the event's Grofomo link. For grfm.to/e/summer-fest the slug is summer-fest.

Your first request

curl -sS \
  -H 'X-Grofomo-Key: pk_live_YOUR_PUBLISHABLE_KEY' \
  'https://api.grofomo.com/v1/events/summer-fest/lineup?surface=web'

Two details in that command do real work:

  • X-Grofomo-Key identifies the organisation. Most organisers now require it; some older ones do not yet. Send it either way — see Authentication.
  • ?surface=web tells the API you are a website. Leave it off and you get the mobile-app surface, which is gated on a different visibility setting, so a perfectly live event can answer 404. This trips up nearly everyone once. See Surfaces.

What comes back

Every response is wrapped in the same envelope:

{
  "apiVersion": 1,
  "data": { "eventSlug": "summer-fest", "slots": [] }
}

Errors use the same envelope with error instead of data:

{
  "apiVersion": 1,
  "error": { "code": "unauthorized", "message": "A valid API key is required" }
}

So the check is always: look at res.ok, then read body.data or body.error. Nothing else varies.

A lineup page

This is a complete Next.js server component. It runs on your server, so it uses the secret key and lets the framework cache the result — a busy festival page then costs you one API call a minute, not one per visitor.

async function getLineup() {
  const res = await fetch(
    'https://api.grofomo.com/v1/events/summer-fest/lineup?surface=web',
    {
      headers: { 'X-Grofomo-Key': process.env.GROFOMO_SECRET_KEY },
      next: { revalidate: 60 },
    },
  );

  const body = await res.json();
  if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
  return body.data;
}

export default async function LineupPage() {
  const lineup = await getLineup();

  return (
    <ol>
      {lineup.slots.map((slot) => (
        <li key={slot.id}>
          <strong>{slot.artist?.name ?? slot.placeholderLabel}</strong>
          {' — '}
          {slot.stage} · {slot.day} · {slot.startTime}
        </li>
      ))}
    </ol>
  );
}

Note slot.artist?.name ?? slot.placeholderLabel. An unannounced set has no artist yet, and rendering "undefined" on a public festival page is a bad afternoon.

Free SEO

On the web surface the lineup, FAQ and news responses each carry a ready-made schema.org block in schemaOrg. Drop it into the page and search engines can read your running order without you modelling any of it:

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(lineup.schemaOrg) }}
/>

What to build next

  • Event detail — dates, venue, stages and brand colours. Usually the first call a site makes.
  • FAQs — answers arrive as sanitised HTML, ready to inject.
  • News — announcement posts, newest first.
  • Ticket availability — live prices and what is sold out.
  • Content version — a cheap probe so you can poll for changes without re-pulling the lineup.