Introduction
A program belongs to one organizer, happens at a venue, and has schedules — weekly, every other week, the 2nd and 4th Tuesday, a list of dates — that become dated occurrences. Its kind is program, event, volunteer or job. Food Web is the system of record: community sites read these endpoints and push new programs back through the suggestions pipeline.
Server-side only
Authentication
Reads work without a credential, but contact email and phone come back null. A key with map:read fills them in, gets its own rate limit and shows up in your usage. Mint keys on the dashboard and send them as Authorization: Bearer fw_…. Pushing programs needs suggestions:write on a partner account.
// app/programs/page.tsx — server component, cached five minutes
export const revalidate = 300;
export default async function Programs() {
const res = await fetch(`https://api.foodweb.network/v1/programs?saved_map=${process.env.FOODWEB_SAVED_MAP}`, {
headers: { Authorization: `Bearer ${process.env.FOODWEB_API_KEY}` },
next: { revalidate: 300 }
});
const { items } = await res.json();
return <ul>{items.map((p) => <li key={p.id}>{p.name} — {p.schedule_label}</li>)}</ul>;
}List programs
GET /v1/programs returns published programs ordered by their next date, with the organizer, venue, facets, a human schedule label (“Every Wednesday · 1–3pm”) and the next occurrence.
curl "https://api.foodweb.network/v1/programs?saved_map=YOUR_MAP_TOKEN&categories=free-food&limit=20" \
-H "Authorization: Bearer $FOODWEB_API_KEY"saved_mapShare token of a public saved map — programs run by the organizations on itentity_idPrograms one organization runs or hostsbbox · near + radius_kmVenue inside a box, or within a radius (≤ 200 km)country · province · cityVenue locality (CA, NS, Halifax)kindprogram, event, volunteer, jobcategories · audiences · access_models · cost_modelsAny of the given valuesfrequencyrecurring or one-timefrom · to · tzHas a date in the range; bare dates use tz (default America/Halifax)qText search over name, summary, organizer and tagslimit · cursorUp to 100 per page; follow next_cursorGive only one of bbox, near or saved_map. Anonymous responses are cached publicly for five minutes and carry an ETag; keyed responses are private.
One program
GET /v1/programs/{idOrSlug} adds the description, eligibility, prices, contact, every schedule (with its RRULE), the next 20 dates, external links, structured_data, an ics_url and the canonical web_url. A cancelled program still answers with its status; an archived one is 410.
curl "https://api.foodweb.network/v1/programs/community-pantry-halifax" \
-H "Authorization: Bearer $FOODWEB_API_KEY"When schedule_confirmed is false, the dates are estimates until the organizer confirms them — show a note beside them.
Calendar
GET /v1/programs/occurrences lists every date in a range of up to 93 days, across the same filters, each with its program’s name, slug, categories and organizer. Times are ISO 8601 in the program’s timezone with its offset. Cancelled dates are included so a calendar can strike them through.
curl "https://api.foodweb.network/v1/programs/occurrences?saved_map=YOUR_MAP_TOKEN&from=2026-11-01&to=2026-12-01&tz=America/Halifax"ICS feeds
Every program has /v1/programs/{slug}/calendar.ics, and /v1/programs/calendar.ics?saved_map=… is a subscribe feed over any list filters (up to 500 programs). Swap https:// for webcal:// to offer a one-tap subscribe link.
JSON-LD
structured_data is schema.org JSON-LD — EventSeries for recurring programs, Event for one-offs, JobPosting for volunteer and job listings — the same markup Food Web’s own pages carry. Embed it verbatim; set your own canonical on your page and leave its url pointing at Food Web.
{program.structured_data && <script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(program.structured_data).replace(/</g, '\\u003c') }}
/>}Partner push
A partner site that reviews submissions in its own queue sends approved ones with POST /v1/suggestions and type: create_program. Inside the partner’s curator area it publishes at once (201); outside it, or with auto_apply: false, it waits for a Food Web curator (202). Send your own id as source_ref so a retry never creates a duplicate.
curl -X POST "https://api.foodweb.network/v1/suggestions" \
-H "Authorization: Bearer $FOODWEB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "create_program",
"payload": {
"kind": "event",
"name": "Harvest Supper",
"organizer": { "entity_id": "9c2e4f10-7d1b-4b8e-8f52-2a6c0e9d3b44" },
"categories": ["community-meal"],
"schedules": [{ "frequency": "dates", "dates": ["2026-11-21"], "start_time": "17:00", "end_time": "19:00" }],
"source_ref": "your-submission-482"
}
}'Errors & limits
Errors look like {"error": {"code": "…", "message": "…"}}.
400 validation_errorA bad parameter: an unknown category, two geo filters, a range over 93 days401 unauthorizedAn unknown or revoked key — never a silent downgrade to anonymous403 insufficient_scopeThe key lacks the scope the route needs (map:read, suggestions:write)404 not_foundNo such program, or it is not public; an unknown or private saved map410 goneThe program was archived422 organizer_requiredA pushed program whose organizer is not a map entity yet429 rate_limit_exceeded120 requests a minute per key; wait Retry-After secondsThe full schema is in the API reference.