Skip to content
On this page and tools

Hosted sites

Hosted sites API

The public endpoints a hosted site's pages call: site status, page views, forms, events, resources and media. They need no key. The site's slug in the address is how Bynefit knows which site you mean.

Available
Who this is for. The runtime and its modules already call these for you. Read this page to build your own markup against them, or to call them from a server. Nothing here exposes member lists, payments, form answers or visitor totals. See the hosted sites guide for what a page can and cannot reach.

Conventions

TopicHow it works
Basehttps://bynefit.com/api/sites
AuthenticationNone. The slug (3 to 100 lowercase letters, numbers and hyphens) names the site.
BrowsersEvery endpoint answers any origin (Access-Control-Allow-Origin: *) and never uses cookies, except secure requests, which answer only the site's own addresses. Send requests without credentials.
ErrorsMost answer 200 with {"ok": false, "error": "..."}, so check ok, not just the status. The widget and events endpoints use real 404 and 500 codes.
MeteringCalls are counted per site per day for usage. They are not throttled, except where noted below.

Which sites answer

Every endpoint decides the same way whether a site is open to the public. A live site is published, not suspended, not archived or deleted or waiting for deletion, and belongs to a Space that is active and not being deleted. Where an endpoint needs a live site, an unknown slug and a site that is not live get the identical answer (form submit also answers a malformed slug with Invalid site), so nobody can learn which sites exist by asking.

EndpointNeedsOtherwise
Site statusA site that is not archived or deleted. Suspended, unpublished and maintenance sites are reported as they are.200 Site not found
Page viewsA site that is not archived or deletedStill a GIF, nothing recorded
Form definitionThe form's site live, or the Space active for a Space-wide form200 Form not found
Form submitA live site, with the form active and belonging to it200 Site not available for a site that is not live; Form not found for an unknown or inactive form
EventsA live site, and the Space's public events setting on404 site_not_found
Ready-made blocksA live site. The events block also needs public events on.404 Not found
Media libraryA live site200 Site not available

Only a Space that is switched off or being deleted stops serving its sites. Turning off public events hides the events endpoint and the events block, and nothing else.

Site status

Request
Method and pathGET /api/sites/runtime-status?slug=my-site
NeedsNothing. Send slug, or host for a custom domain.
curl "https://bynefit.com/api/sites/runtime-status?slug=my-site"
200JSON
{
  "ok": true,
  "slug": "my-site",
  "kind": "hosted",
  "status": {
    "is_suspended": false,
    "is_maintenance": false,
    "maintenance_message": null,
    "published": true,
    "skip_branding": false,
    "badge_href": null
  }
}

Page views

Request
Method and pathGET /api/sites/track?slug=my-site&path=/about
NeedsNothing

Always answers a 1 by 1 transparent GIF, whatever you send, so it can be fired as an image: new Image().src = .... It stores the page path (without query string or fragment), the referring address, the browser's user agent and a hash of the visitor's IP address. No cookies and no raw IP address. Views are recorded for any site that has not been archived or deleted, including one that is unpublished or suspended; an archived or deleted site records nothing. Visitors or sites that send too many views in a minute have the extra ones dropped silently. The totals are shown to the Space owner in the dashboard; there is no endpoint to read them.

Form definition

Request
Method and pathGET /api/sites/forms/{form_id}/schema
NeedsNothing. {form_id} looks like frm_AbC123xYz
200JSON
{
  "ok": true,
  "form": {
    "id": "frm_AbC123xYz",
    "name": "Contact us",
    "team_name": "Riverside Aid",
    "confirmation_message": null,
    "fields": [
      { "id": 12, "label": "Email", "type": "email", "required": true, "placeholder": null, "help": null, "options": null },
      { "id": 13, "label": "Topic", "type": "dropdown", "required": false, "placeholder": null, "help": null, "options": ["Housing", "Food", "Other"] }
    ]
  }
}

Field types: text, textarea, email, tel, number, date, dropdown, checkbox, radio, section_header, hidden. Only active forms answer, and only while the form is open: a form tied to a site needs that site live, and a Space form needs an active Space. Anything else answers {"ok": false, "error": "Form not found"}, the same as an unknown form. A malformed id answers Invalid form id. Cached for 60 seconds.

Submit a form

Request
Method and pathPOST /api/sites/{slug}/forms/{form_id}/submit
NeedsNothing. The form must be active and belong to the site, and the site must be live: published, not suspended, not archived or deleted, and its Space active.

Send a form-encoded body (application/x-www-form-urlencoded or multipart), not JSON. A value is matched to a field by field_{id}, or by the field's label lowercased with anything that is not a letter or number turned into _ (so Full name is full_name). Checkbox groups can repeat the name (topic[]); the values are joined with a comma. Names that match no field are ignored.

curl -X POST https://bynefit.com/api/sites/my-site/forms/frm_AbC123xYz/submit \
  -d "full_name=Sam Rivera" \
  -d "email=sam@example.com" \
  -d "message=Do you have evening hours?"
200JSON
{ "ok": true, "message": "Thanks! We received your submission." }

The message is the form's own confirmation message when it has one. Nothing else comes back: no submission id and no echo of what was sent.

Forms that ask for an email code

The schema answer carries auth_mode. When it is email_otp, a submit without a confirmed email answers 403 with "code": "email_code_required". The Bynefit form module handles this for you: it shows a short first step, sends the code, and unlocks the form with the confirmed address already in the first email field. A form that asks people to sign in (bynli_login) answers 403 with "code": "auth_required" and the address of its Bynefit form page instead.

Request
Method and pathPOST /api/sites/{slug}/forms/{form_id}/otp
NeedsNothing. The form must be an email-code form on a live site.

Form-encoded body with action=send and email: a six digit code is emailed and {"ok": true, "challenge": "..."} comes back. Codes are limited to one a minute per address and form, 5 an hour per address and 200 an hour per form, and each caller address is throttled; over a limit answers 429. Then action=verify, challenge and code: {"ok": true, "ticket": "...", "email": "..."}. Five wrong tries lock a code. Send the ticket as _bynli_otp with the submit. It is a bearer token: good for an hour, for that form only, for any number of submissions. The submission is stored under the confirmed address, and the form's first email field is replaced by it. An unknown or non-email-code form answers 404 or 400, and an expired ticket answers 403 with email_code_required so the form asks again.

StatusBodyMeaning
200{"ok":false,"error":"Please fill in: Email","missing":["Email"]}A required field is empty. missing lists the labels.
200{"ok":false,"error":"Invalid site"}The slug in the address is malformed.
200{"ok":false,"error":"Site not available"}The site is unknown, unpublished, suspended, archived or deleted, or its Space is inactive or being deleted. All of these answer the same.
200{"ok":false,"error":"Form not found"}The form is unknown or not active. A malformed id answers Invalid form. A form that belongs to another Space or is not enabled for this site answers Form does not belong to this site or Form not enabled on this site.
403{"ok":false,"code":"email_code_required","error":"Confirm your email with the code we send you before submitting this form."}The form is an email-code form and the submit carried no valid _bynli_otp ticket.
403{"ok":false,"code":"auth_required","form_url":"..."}The form needs people to sign in, which only works from its Bynefit form page.
429{"ok":false,"error":"Too many submissions. Please try again in a minute."}More than 5 submissions to the same form from one address in a minute.
500{"ok":false,"error":"Server error. Please try again."}Something failed on our side. Retry.

What a submission sets off

The server only checks that required fields are filled. It does not check that an email looks like an email, that a number is a number, or that a dropdown value is one of the options, so validate in the page as well.

Events

Request
Method and pathGET /api/sites/{slug}/events?limit=5&scope=upcoming
NeedsNothing

limit is 1 to 50 (default 5). scope is upcoming (default) or past. Only a live site answers, and only when the Space has turned on public events. Otherwise the answer is the same 404 {"ok": false, "error": "site_not_found"} as for an unknown or malformed slug, so the answer never reveals which sites exist. Cancelled and draft events are left out. upcoming covers today up to two years ahead and past reaches back two years, newest first.

curl "https://bynefit.com/api/sites/my-site/events?limit=3&scope=upcoming"
200JSON
{
  "ok": true,
  "team": { "slug": "riverside-aid", "name": "Riverside Aid" },
  "scope": "upcoming",
  "count": 1,
  "events": [
    {
      "id": 481,
      "title": "Free tax help night",
      "description": "Bring your documents.",
      "event_date": "2026-11-04 18:00:00",
      "event_date_iso": "2026-11-04T18:00:00+00:00",
      "location": "Community hall",
      "tickets_enabled": true,
      "ticket_price": 0.0,
      "ticket_name": "General",
      "tickets_sold": 12,
      "tickets_limit": 40
    }
  ]
}

Errors use real codes: 404 site_not_found for an unknown, malformed or gated site (one answer for all, so it never reveals which sites exist), and 500 server_error. At most 50 events come back. Cached for 60 seconds. Sold counts and limits come with every event listed. There are no attendee lists or buyers in the answer. Ticket and detail links on a hosted site go to the event's page on Bynefit.

Ready-made blocks

Request
Method and pathGET /api/sites/widget/{slug}/{kind}
NeedsNothing. {kind} is events or resources.

Returns finished HTML in a JSON field, for dropping into a page. Any other kind, or any site that is not live, answers 404 with {"ok": false, "error": "Not found"}, the same body for each, and so does the events block when the Space has not turned on public events. Cached for 60 seconds.

KindOptionsAnswer
eventsscope (upcoming or past), limit 1 to 50, default 5{"ok":true,"enabled":true,"html":"...","count":N}. Needs a live site and public events turned on, or it answers 404. Cancelled and draft events are left out, within the same two-year window as the events endpoint. count is the number of upcoming events, not the number shown.
resourcescategory (letters, numbers, spaces, _ and -, up to 40), limit 1 to 50, default 12{"ok":true,"html":"...","count":N}. The Space's most-upvoted approved resources, filtered by category.

The HTML is escaped on our side. Set it with innerHTML only for these two endpoints, never for text from anywhere else.

Media library

Request
Method and pathGET /api/sites/{slug}/media
NeedsNothing. The site must be live.
200JSON
{
  "ok": true,
  "site": "my-site",
  "count": 1,
  "media": [
    {
      "id": 7,
      "filename": "team.jpg",
      "url": "https://my-site.site.bynefit.com/media/team.jpg",
      "mime_type": "image/jpeg",
      "size_bytes": 184233,
      "uploaded_at": "2026-10-08 09:12:44"
    }
  ]
}

Newest first, with no paging, so a big library is a big answer. Not cached. An unknown, malformed or not-live site answers {"ok": false, "error": "Site not available"}.

Secure requests

Request
Method and pathGET /api/sites/my-site/proxy/weather
NeedsNothing from the page. The site owner names the request, its service and its methods in the dashboard, and the call must come from a page on the site.

The one endpoint here that is not open to every origin. It forwards a page's call to the service the site owner chose and adds the owner's secret on the server, so a page can use a paid or private API without holding its key. It accepts only the methods the owner allowed, answers only the site's own addresses, and has per-visitor, per-minute and per-day limits. The full contract, limits and error codes are in Keeping keys safe on a hosted site.

What is not here