Form definition
GET
/v1/forms/{key}A published form's definition — its fields, types, labels and validation — so you can render it in your own design instead of embedding a hosted page.
Artist-picker fields arrive with their options already filled in from the event's lineup.
Send ?surface=webOmit it and the request is treated as coming from the mobile app, which is gated on a different visibility setting — so a perfectly live event can return
404 not_found for no visible reason.Request
curl -sS \
-H 'X-Grofomo-Key: pk_live_YOUR_PUBLISHABLE_KEY' \
'https://api.grofomo.com/v1/forms/volunteer-signup?surface=web'Path parameters
keystringrequired- The form's key, from the organiser's console. Example: `volunteer-signup`.
Query parameters
surface'web'required- Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404. Example: `web`.
Response
200 OK
{
"apiVersion": 1,
"data": {
"key": "volunteer-signup",
"kind": "contact",
"eventId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
"eventSlug": "summer-fest",
"title": "Volunteer with us",
"version": 3,
"surface": "web",
"fields": [
{
"id": "full_name",
"type": "text",
"label": "Your name",
"required": true
},
{
"id": "email",
"type": "email",
"label": "Email address",
"required": true,
"role": "email"
},
{
"id": "shifts",
"type": "checkbox",
"label": "Which shifts can you cover?",
"help": "Pick as many as you like.",
"required": true,
"options": [
{
"value": "friday-evening",
"label": "Friday evening"
},
{
"value": "saturday-morning",
"label": "Saturday morning"
},
{
"value": "sunday-close",
"label": "Sunday close"
}
]
},
{
"id": "favourite_act",
"type": "artist_multiselect",
"label": "Anyone you would love to work near?",
"required": false,
"source": "lineup",
"options": [
{
"value": "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a13",
"label": "Nala Vex"
},
{
"value": "d1f3b5a7-9c2e-4d80-8b6f-5a3c1e7d9b04",
"label": "Sonny Delrey"
}
]
}
]
}
}keystring- The form key. Post submissions back to this same key.
kind"feedback" | "mailing_list" | "contact" | "merch_enquiry"- What the form is for.
eventIduuid | null- Null for an organisation-wide form not tied to one event.
eventSlugstring | nulltitlestringversionnumber- Definition version. Send it back with the submission so the answers can be audited against it.
surface"app" | "web"fieldsobject[]- The questions, in the organiser's order.
fields[].idstring- Key this answer under this id when you submit.
fields[].type"rating" | "radio" | "checkbox" | "select" | …- Which control to render.
fields[].labelstring- The question, as the organiser wrote it.
fields[].helpstringmay be absent- Hint text to show under the control.
fields[].requiredbooleanmay be absent- Absent means optional.
fields[].role"overall_rating" | "venue_rating" | "would_return" | "favourite_artist" | … | nullmay be absent- What the answer means to Grofomo, e.g. `email` promotes it to the contact address.
fields[].optionsobject[]may be absent- Choices for a radio, checkbox, select or artist picker. Submit the `value`, display the `label`.
fields[].options[].valuestringfields[].options[].labelstringfields[].minnumbermay be absent- Lower bound of a rating.
fields[].maxnumbermay be absent- Upper bound of a rating.
fields[].source"lineup" | "stages" | "merch_items"may be absentmerchCatalogobject[]may be absentmerchCatalog[].valuestring- Product id. Matches the option value on a `merch_items` form field.
merchCatalog[].namestringmerchCatalog[].garmentTypestring- What the garment is, e.g. "Heavyweight cotton t-shirt".
merchCatalog[].descriptionstring | nullmerchCatalog[].pricePenniesnumber- Unit price in minor units, so 2500 is £25.00.
merchCatalog[].currencystring- ISO 4217 code, e.g. "GBP".
merchCatalog[].sizesstring[]- Available sizes, in display order.
merchCatalog[].coloursobject[]- Colourways. Each carries the PLAIN garment image; overlay the event logo yourself.
merchCatalog[].colours[].labelstring- Colourway name, e.g. "Bone".
merchCatalog[].colours[].hexstring | null- Swatch colour, for a colour picker.
merchCatalog[].colours[].frontImageUrlstring | null- Plain garment front, with no logo applied.
merchCatalog[].colours[].backImageUrlstring | null- Plain garment back, with no logo applied.
merchCatalog[].colours[].logoColorstring | nullmay be absent- How to treat the logo on this colourway: `auto`, `brand`, `white` or `black`.
merchCatalog[].colours[].outOfStockbooleanmay be absent- Show the colourway, but do not let anyone buy it.
merchCatalog[].printZonesobject[]- Where to place the logo on each side. All coordinates are 0–1 fractions, so a preview scales freely.
merchCatalog[].printZones[].side"front" | "back"merchCatalog[].printZones[].xnumber- Centre of the logo, as a 0–1 fraction of the garment image width.
merchCatalog[].printZones[].ynumber- Centre of the logo, as a 0–1 fraction of the garment image height.
merchCatalog[].printZones[].scalenumber- Logo width, as a 0–1 fraction of the garment image width.
merchCatalog[].printZones[].recolorbooleanmay be absentmerchCatalog[].printZones[].logoUrlstring | nullmay be absent- A logo specific to this placement. Absent means use the event logo.
merchCatalog[].printZones[].logoColorstring | nullmay be absent- `auto`, `brand`, `white` or `black`.
merchCatalog[].printZones[].clipbooleanmay be absent- Mask the logo to the garment silhouette, so a wide placement does not bleed off the edge.
merchCatalog[].outlineobject[]may be absent- Traced garment silhouette (0–1 points) driving the `clip` mask. Absent means use a default tee shape.
merchCatalog[].outline[].xnumbermerchCatalog[].outline[].ynumber
Generated from PublicFormResponse
Errors
unauthorizedHTTP 401- No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.
origin_not_allowedHTTP 403- Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.
not_foundHTTP 404- No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.
Notes
- Rate limited to roughly 60 requests per minute per caller. Guidance, not a contract — see caching and rate limits.