Skip to content

API and feeds

Everything we verify is available as structured data, free and without a key. If you build something with it, we would love to hear about it.

JSON API

GET https://hackalendar.com/api/events

Returns upcoming events as { data, meta }. CORS is open and responses are cached. Use meta.nextOffset to continue while meta.hasMore is true. meta.total is the number of rows in this page, not the total catalogue size.

Dates preserve what the organiser published: startAt and endAt use YYYY-MM-DD when no hour was supplied, or an ISO timestamp for a known time. The optional temporalPrecision field describes each boundary. An unknown end is null; timezone is also null for periods with calendar dates only. Their final day is included.

Editorial picks are a smaller human selection within the catalogue. Each event includes picked and pickedAt, an ISO timestamp or null. A pick does not require a written note.

ParameterMeaning
cityOne city slug, e.g. paris.
themeTheme key: ai, web3, climate, health, data, hardware, student, creative.
mode`in_person`, `online` or `hybrid`.
fromISO date. Narrows the window forward; earlier than today is clamped to today.
toISO date, the far end of the window.
limitMaximum rows, 1–200. Defaults to 100.
offsetPagination offset, 0–100000. Defaults to 0; continue with meta.nextOffset.

Private subscriptions and contributions

Create private access without a required account or email. Keep the management link safe, then give your integration a revocable key with the scopes it needs. API keys use Authorization: Bearer YOUR_API_KEY. Never put the key in a query string or request body. These responses are private and not cached. MCP clients can use the corresponding tools and OAuth flow.

RouteScopePurpose
POST /api/submissionscontributions:writePropose an event URL and optional context; returns a private receipt.
POST /api/feedbackscontributions:writeReport a correction or suggestion; no direct listing changes.
GET /api/submissions/:idcontributions:readRead the status of your own contribution.
GET /api/subscriptionssubscriptions:readList your subscriptions.
POST /api/subscriptionssubscriptions:writeSave criteria and receive current matches plus a cursor.
GET /api/subscriptions/:idsubscriptions:readRead one subscription and its followed events; preserve the existing inbox cursor.
PATCH /api/subscriptions/:idsubscriptions:writeReplace name, criteria and active state.
DELETE /api/subscriptions/:idsubscriptions:writeRemove a subscription.
GET /api/notifications?cursor=…notifications:readRead your notification inbox; limit 1–100, default 30.
GET /api/notifications/statenotifications:readRead all followed events and a fresh inbox cursor atomically; no parameters.
GET /api/webhookswebhooks:readList your endpoints and delivery health.
POST /api/webhookswebhooks:writeRegister an HTTPS endpoint; signing secret returned once.
PATCH /api/webhooks/:idwebhooks:writeaction: verify, pause, resume or rotate_secret.
DELETE /api/webhooks/:idwebhooks:writeDelete an endpoint and its delivery history.
GET /api/webhooks/:id/deliverieswebhooks:readInspect the latest 50 delivery attempts.
POST /api/webhooks/:id/deliverieswebhooks:writeRetry a failed delivery with its deliveryId.

Submitting an event

{
  "url": "https://example.org/hackathon",
  "context": "Student teams are welcome.",
  "idempotencyKey": "your-unique-request-001"
}

Send JSON to POST /api/submissions; contactEmail is optional. A successful deposit returns HTTP 202 with id, status: received, published: false, receivedAt and statusUrl. A curator reviews the proposal before publication. You need not be its organiser.

Feedback uses message, optional eventSlug, category (correction, broken_link, suggestion, other) and evidenceUrl. Both write endpoints require an idempotencyKey of 8–120 letters, digits or . _ : -. REST also accepts the same key as Idempotency-Key. Retry identical content with the same key; conflicting content returns 409. Retrying does not duplicate a message or use another quota slot.

Notes have a 4,000-character limit, URLs 2,000 and contact emails 254; feedback needs at least 10 characters. JSON bodies are bounded to 64 KiB. Contribution admission allows five per owner and network source per hour, plus a shared 200-per-hour service limit. HTTP 429 includes Retry-After. HTTP 401/403 means missing access or scope, 400 means invalid fields, 413/415 means body size/media type, and 503 means temporary unavailability. Errors contain error and message; no private moderation details are returned.

Following events

A subscription contains name, active and criteria.rules. Each rule accepts cities (city slugs), modes and themes. Values within each facet are OR; facets are AND; rules are OR. Hybrid matches both online and in-person. Use separate rules for Paris in-person and online worldwide. Existing matches form a starting snapshot, not an initial webhook burst. The cursors returned by subscription creation, edits and single-subscription reads cover that subscription only: preserve an existing inbox cursor to keep unread changes from other subscriptions reachable.

For a first sync, call /api/notifications/state and save its events and cursor. Poll /api/notifications with that cursor and follow hasMore. Publication, important changes, cancellation, withdrawal and restoration share versioned notification IDs. History is retained for 90 days; HTTP 410 means the cursor expired. Call /api/notifications/state again, replace local state with its authoritative snapshot of all active subscriptions, then poll with its new cursor. Delivery is asynchronous. Webhook receivers must verify signatures, deduplicate notification IDs and apply event revisions monotonically. Registering an endpoint requires verification before any event delivery. The agent guide explains the tool workflow.

Webhook receivers

Register an HTTPS endpoint on port 443 with a name and URL. Keep the returned signing secret; it is shown once. Verification sends a signed webhook.challenge object containing challenge. Return HTTP 2xx with JSON containing that exact challenge to activate delivery. Redirects and private network addresses are refused.

Requests carry x-hackalendar-id, x-hackalendar-timestamp (Unix seconds) and x-hackalendar-signature. Verify the signature against the raw request body, before parsing or reserializing it:

v1=HMAC-SHA256(signingSecret, timestamp + "." + rawBody).hex()

Compare signatures in constant time and reject stale timestamps within a tolerance suitable for your clock, such as five minutes. Each delivery attempt is signed with its current timestamp; the notification ID remains stable. Respond with 2xx only after durable acceptance, then process the event asynchronously. Transient failures retry with capped backoff; persistent failures are visible in delivery history and can pause an endpoint. Rotation requires verification again.

Event descriptions in webhook event and previousEvent snapshots are capped at 1,000 characters, with descriptionTruncated when shortened. The serialized webhook body is limited to 64 KiB. An oversized notification fails that delivery without pausing the endpoint; the complete notification remains readable from the private inbox.

Feeds

  • /feed.xml — every upcoming hackathon, and /{city}/feed.xml per city.
  • /calendar.ics — subscribe in Google or Apple Calendar; /{city}/calendar.ics per city.
  • /llms.txt — a summary of the site for language models.

Using it

Please link back to the organiser’s registration page rather than replacing it — the whole point of this project is sending people to the events. Attribution to Hackalendar is appreciated but not required. Data is provided as-is, and we correct mistakes quickly when someone tells us about one.