Br1gade API for your website and tools

For the web developer, quote-tool admin or Zapier builder at a restaurant or catering business that uses Br1gade.

Br1gade can take two things from your own systems: event requests (a catering quote or private-party booking from your website’s form, your quote tool or a Zapier flow) and job applications (from your careers page or an applicant tracking tool). Each lands in the business’s Br1gade for a manager to act on. Nothing is scheduled or hired automatically.

1. Get a key

An Owner or Manager makes a key in the Br1gade app: Schedule › Settings › API Keys › New Key. They choose a name (say, “Website form”) and what it may do, its scopes: event_requests (section 2), applications (section 3), availability (section 8), holds and events (sections 9 and 10, for a quote or booking tool that places real bookings). A key can also be pinned to one location; it then sees only that location. The key is shown once; copy it then. It looks like b1_live_ followed by 40 characters.

Keep it on your server or in your tool’s settings, never in a web page’s JavaScript. If it leaks, revoke it in the same place and make a new one; everything the old key sent stays, marked with that key.

Limits: 120 calls an hour per key, or 600 for a key with the availability scope (availability is meant to be polled). The count covers everything the key calls. Over that, you get 429 and the message to try again later.

2. Send an event request

POST https://api.br1gade.com/v1/events/request
Authorization: Bearer b1_live_...
Content-Type: application/json
{
  "request": {
    "contact_name": "Jane Doe",
    "contact_phone": "501-555-0123",
    "customer_email": "jane@example.com",
    "event_type": "WEDDING",
    "starts_at": "2026-11-14T22:00:00Z",
    "ends_at": "2026-11-15T03:00:00Z",
    "guest_count": 120,
    "remote": true,
    "address_line1": "12 Garden Lane",
    "city": "Little Rock",
    "state": "AR",
    "postal_code": "72201",
    "customer_notes": "Plated dinner, open bar until 11.",
    "location_id": null
  }
}
Field Required Notes
contact_name yes The customer
contact_phone, customer_email one of them The email gets a “we got your request” confirmation from Br1gade
event_type yes One of the keys below
starts_at, ends_at yes ISO 8601 with a time zone. In the future, end after start, a week at most
guest_count no 1 to 5000
remote no true: the event is at the customer’s address (a Mobile Event); then the address fields are required. false (default): at the business
address_line1, address_line2, city, state, postal_code when remote US addresses; state is the two-letter code
customer_notes no Free text
location_id no For a business with several locations; otherwise its only location is used. A key made for one location always uses that one

Event types: WEDDING, CORPORATE_MEETING, CONFERENCE, BIRTHDAY_PARTY, ANNIVERSARY, HOLIDAY_PARTY, FUNDRAISER, BABY_SHOWER, BRIDAL_SHOWER, GRADUATION, FUNERAL_MEMORIAL, RELIGIOUS_CELEBRATION, PRODUCT_LAUNCH_GRAND_OPENING, EMPLOYEE_APPRECIATION, TEAM_BUILDING, FESTIVAL, FAIR, SPORTING_EVENT, PRIVATE_DINNER_PARTY, COCKTAIL_PARTY, OTHER.

Response 200: { "success": true, "event_id": "…", "business_name": "Boars Nest" }. The request appears in the business’s Events as a request; the managers are notified.

3. Send a job application

POST https://api.br1gade.com/v1/jobs/apply
Authorization: Bearer b1_live_...
Content-Type: application/json
{
  "application": {
    "first_name": "Avery",
    "last_name": "Applicant",
    "email": "avery@example.com",
    "phone": "501-555-0166",
    "role": "SERVER",
    "hiring_need_id": null,
    "screening": [{ "position_question_id": "…", "answer": "Yes" }],
    "cover_note": "Three years at a wine bar.",
    "resume_url": "https://example.com/avery.pdf",
    "availability": { "weekends": true },
    "expected_wage": 18,
    "external_id": "your-system-id-123"
  }
}
Field Required Notes
first_name yes
email, phone one of them
role or hiring_need_id one of them A role key such as SERVER, BARTENDER, LINE_COOK, HOST, DISHWASHER (the business’s open job for that role is used), or the job’s id from GET /v1/jobs. 404 NO_OPEN_JOB when there is none
last_name, cover_note, resume_url, availability (any JSON), expected_wage no
external_id no Your own id for the application. Sending the same one again returns the first application with "duplicate": true and creates nothing

Response 200: { "success": true, "application_id": "…", "hiring_need_id": "…", "status": "INTERVIEW", "duplicate": false }. The applicant appears in the business’s Hiring; the hiring managers are notified; the applicant gets a confirmation email, and an interview invitation when they passed the screening and the business invites automatically.

4. Hiring on your own website

Two ways, depending on who builds your site.

No developer: link your site’s “Careers” or “Jobs” button to your Br1gade careers page, https://br1gade.com/careers/<your-address>. It lists your open jobs, takes applications with your screening questions, and lets applicants book interviews. Turn it on in Br1gade: Schedule › Settings › Careers Page. To show the jobs on your own page without any coding, paste this where they should appear:

<div id="br1gade-jobs" data-business="<your-address>"></div>
<script src="https://br1gade.com/embed/jobs.js" async></script>

Each job links to its page on br1gade.com, where people apply. Options: data-heading="Join the team", data-empty="No openings right now", data-theme="dark"; the elements carry br1gade- classes you can style.

A developer, your own design: your site renders the jobs, takes the application and books the interview, and the applicant never leaves it. Three calls, no key needed for the reads:

Call Does
GET https://api.br1gade.com/v1/jobs?business=<your-address> Your open jobs: id, title, description, role, position_type, pay (min, max, frequency; absent when you hide pay), openings, is_urgent, posted_at, questions (id, text, format SELECT / MULTISELECT / TEXT, options, required), url, apply_url. accepting_applications says whether the page is on. Cached five minutes.
POST https://api.br1gade.com/v1/jobs/apply (section 3, with your key) The application, with hiring_need_id from the list and screening: [{ "position_question_id": "<question id>", "answer": "Yes" }] (an array of strings for MULTISELECT). The response’s status is INTERVIEW when the person passed and was invited, SHORTLISTED when they passed and you invite by hand, DECLINED when they did not pass.
GET https://api.br1gade.com/v1/interviews/{token} The booking link’s contents: applicant_name, business_name, job_title, timezone, slots (id, starts_at, ends_at, is_virtual, location_text) for the next two weeks, and booking when one exists.
POST https://api.br1gade.com/v1/interviews/{token}/book { "slot_id": "…" } Books it. 409 SLOT_UNAVAILABLE when the time just went. Rebooking frees the earlier slot.

The interview invitation and reminder emails carry the booking link. To send applicants to your own site instead of ours, set the booking address in Br1gade (Schedule › Settings › Hiring Automation › Interview booking page) to your page with {token} in it, for example https://yourvenue.com/interview?token={token}; your page then calls the two interview endpoints with that token. Emails to applicants are sent by Br1gade on your behalf either way.

If you build it yourself, a line such as “Hiring with Br1gade” linking to https://br1gade.com somewhere on the page is appreciated; it is not required.

5. Errors

Every refusal is JSON with success: false, an error code and a message you can show.

Status Code Meaning
401 INVALID_KEY No key or a wrong key
401 KEY_REVOKED The key was revoked in Settings
403 SCOPE_REQUIRED The key lacks the scope for this call; scope names it
429 TOO_MANY_REQUESTS More than 120 calls this hour (600 with availability), or too many bad keys from your address
404 LOCATION_NOT_FOUND No such location for this key: another business’s, inactive, or not the location the key is pinned to
404 HOLD_NOT_FOUND, EVENT_NOT_FOUND No such hold or event for this key (another business’s, another location for a pinned key, or a malformed id)
409 CAPACITY_FULL The day is booked at that location (section 8); the answer carries date, events_count, max_events_per_day
409 HOLD_EXPIRED, HOLD_NOT_ACTIVE, HOLD_MISMATCH The hold ran out; was already converted, released or expired; or is for another location or day than the event
409 EXTERNAL_ID_CONFLICT That external_id was already used with a different body; the answer carries the existing hold_id or event_id
409 EVENT_POSTED, EVENT_CANCELLED, EVENT_COMPLETED A posted event’s times, staffing and location are fixed; a cancelled or closed event cannot change
400 EXTERNAL_ID_REQUIRED, INVALID_HOLD, INVALID_EVENT, INVALID_NEED, INVALID_GUESTS, INVALID_STATUS, INVALID_CURSOR Fix the field named (field or message says which)
400 INVALID_DATE_RANGE, INVALID_BODY from/to are not YYYY-MM-DD, out of order or more than 366 days apart; the body is not a JSON object or is over 64 KB
400 CONTACT_REQUIRED, INVALID_EMAIL, INVALID_EVENT_TYPE, INVALID_TIMES, INVALID_GUESTS, ADDRESS_REQUIRED, LOCATION_REQUIRED Fix the field named
404 NO_OPEN_JOB No open job for that role
400 MISSING_ANSWER, INVALID_ANSWER A required screening question was not answered, or an answer is not one of its choices; question names it
403 CAREERS_CLOSED The careers page is turned off
500 Something on our side; try again, and tell us if it keeps happening

6. Zapier, Make, and web forms

Use a “Webhooks” or “HTTP request” step: method POST, the URL above, headers Authorization: Bearer b1_live_... and Content-Type: application/json, and a JSON body shaped as in section 2 or 3. For a plain HTML form on your site, post to your own server first and have it make the call: the key must not be in the page.

If you only want a form and have no developer, skip the API: turn on the hosted request page in Settings and link to https://br1gade.com/book/<your-address>.

7. Webhooks

Br1gade can call your server when something happens, so you do not have to poll. Owners and Managers add endpoints in the app under Settings > API Access > Webhooks (a Team feature, with the same 30-day trial as API keys): an https:// address, a label, and the events to send. Each endpoint gets a secret.

Event When Payload key
event_request.received A request arrived from your site, the hosted page or the API event_request
event_request.posted A manager accepted it and posted the event event_request
event_request.declined A manager declined it (cancel_reason) event_request
application.received An application arrived application
application.status_changed Any status change (previous_status, status, declined_reason, interview_scheduled_at) application
application.hired The applicant was hired application

A delivery is a POST with Content-Type: application/json and these headers:

X-Br1gade-Event: application.status_changed
X-Br1gade-Delivery: 01a1…                      (unique per delivery; use it to ignore repeats)
X-Br1gade-Signature: sha256=<hex>              (HMAC-SHA256 of the exact request body, keyed with the endpoint's secret)

Verify the signature before trusting a delivery:

import { createHmac, timingSafeEqual } from "node:crypto";
const expected = "sha256=" + createHmac("sha256", process.env.BR1GADE_WEBHOOK_SECRET).update(rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-br1gade-signature"] ?? ""));

Answer with any 2xx within 10 seconds. Anything else is retried after 1 minute, 10 minutes, 1 hour and 6 hours, then marked failed; after 20 failures in a row the endpoint is switched off until you re-enable it in Settings, where the last ten deliveries and what your server answered are shown. “Send a test” sends a ping.

Three more types for a quote or booking tool (sections 8 to 10):

Type When Carries
availability.changed A hold is placed, extended past its date, released, converted or expires; a draft or posted event appears, moves to another day or location, or stops counting (cancelled, closed) availability: { location_id, dates: ["2027-05-15", …] }: re-read those days
event.updated Anything on an event changed, whoever changed it: times, status, address, guests, tier, add-ons, drinks, named problems, notes, or a person was put on or taken off a shift booked_event (the event object of section 10), changed: ["guest_count", …] ("crew_assigned" for staffing), and previous_status when the status changed
event.closed The manager closed the event (section 11) booked_event with actuals filled, changed, previous_status: "POSTED"

Your own API calls raise these too; ignore the echo by external_id and modified_at. Retries carry the same delivery_id, so store it and answer 200 to a repeat.

Body example:

{
  "event": "application.status_changed",
  "business_id": "…",
  "occurred_at": "2026-10-06T20:11:03Z",
  "delivery_id": "01a1…",
  "previous_status": "APPLIED",
  "application": { "id": "…", "status": "INTERVIEW", "hiring_need_id": "…", "role": "SERVER", "first_name": "Pat", "last_name": "Lee", "email": "pat@example.com", "phone": "+15015550100", "source": "API", "pre_screen_passed": true, "declined_reason": null, "interview_scheduled_at": null, "external_application_id": "your-id-123", "created_at": "…" }
}

8. Availability

Which days a location can take an event, for a quote or booking tool. Needs the availability scope.

GET https://api.br1gade.com/v1/availability?location_id=<uuid>&from=2027-05-01&to=2027-05-31
Authorization: Bearer b1_live_...
Query Required Notes
location_id if the business has more than one location Omit it for a key pinned to a location
from, to no YYYY-MM-DD in the location’s time zone; default today and the next 30 days; at most 366 days

Response 200:

{
  "success": true,
  "location_id": "…",
  "timezone": "America/Chicago",
  "max_events_per_day": 1,
  "from": "2027-05-01",
  "to": "2027-05-31",
  "days": [
    { "date": "2027-05-01", "status": "OPEN", "events_count": 0, "holds_count": 0 },
    { "date": "2027-05-15", "status": "BOOKED", "events_count": 1, "holds_count": 0 }
  ]
}

A day is BOOKED when the location’s draft and posted events starting that day reach its events per day setting (Schedule › Settings › Events Per Day; max_events_per_day is null when the location has no cap, and then a day is never booked), HELD when a live hold exists on it (section 9), otherwise OPEN. A multi-day event counts on the day it starts. The answer carries Cache-Control: private, max-age=300; poll it no more than every few minutes.

9. Holds

A hold keeps a date while a quote is out. It makes the day HELD in section 8 and lets the booking in section 10 go through even if the day fills up meanwhile. Needs the holds scope.

POST https://api.br1gade.com/v1/holds
Authorization: Bearer b1_live_...
Content-Type: application/json
{
  "external_id": "quote_8841",
  "location_id": "…",
  "date": "2027-05-15",
  "expires_at": "2027-04-20T17:00:00Z",
  "note": "Smith wedding, 120 guests",
  "client": { "name": "Jane Smith", "phone": "501-555-0123", "email": "jane@example.com" }
}
Field Required Notes
external_id yes Your own id for the quote. Sending the same external_id with the same body again returns the same hold with 200; a different body is 409 EXTERNAL_ID_CONFLICT
location_id if the business has more than one location Omit for a pinned key
date yes YYYY-MM-DD in the location’s time zone, today or later
expires_at yes When the hold lapses, UTC; a job expires it within a minute of that
note, client no Shown to the manager; client is an object with name, phone, email

Response 201 (or 200 when it already existed): { "success": true, "hold": { "id": "…", "external_id": "quote_8841", "location_id": "…", "date": "2027-05-15", "status": "ACTIVE", "expires_at": "…", "note": "…", "client": {…}, "converted_event_id": null, "created_at": "…", "modified_at": "…" } }. A hold on a day that is already BOOKED is refused with 409 CAPACITY_FULL. Holds never block other holds.

PATCH /v1/holds/<id> with { "expires_at": "…" } (and optionally note) extends an active hold. DELETE /v1/holds/<id> releases it (status: RELEASED; releasing twice is fine). A converted or expired hold answers 409 HOLD_NOT_ACTIVE. Statuses: ACTIVE, CONVERTED (an event was booked from it), RELEASED, EXPIRED.

10. Events

Book a paid quote as a real event, read it, change it, cancel it. Needs the events scope. The event lands as a draft for the manager, who adds staffing and posts it; nothing is scheduled by the API itself.

POST https://api.br1gade.com/v1/events
Authorization: Bearer b1_live_...
Content-Type: application/json
{
  "external_id": "quote_8841",
  "hold_id": "…",
  "location_id": "…",
  "name": "Smith wedding",
  "event_type": "WEDDING",
  "starts_at": "2027-05-15T22:00:00Z",
  "ends_at": "2027-05-16T03:00:00Z",
  "remote": true,
  "address_line1": "12 Garden Lane", "city": "Little Rock", "state": "AR", "postal_code": "72201", "country": "US",
  "guest_count": 120,
  "contact_name": "Jane Smith", "contact_phone": "501-555-0123", "customer_email": "jane@example.com",
  "customer_notes": "Plated dinner, open bar until 11.",
  "tier": "black_tie", "add_ons": ["champagne_toast"], "signature_drinks": ["Garden Spritz"], "named_problems": [],
  "quoted_price_cents": 250000,
  "needs": [{ "role": "BARTENDER", "headcount": 2 }, { "role": "SERVER", "headcount": 4, "start_at": "2027-05-15T21:00:00Z", "end_at": "2027-05-16T03:00:00Z" }]
}
Field Required Notes
external_id yes Your id; same rules as holds (same body → 200 with the existing event, different body → 409)
hold_id no A live hold for the same location and day; it becomes CONVERTED in the same transaction and lets the booking through even if the day is now BOOKED
location_id if more than one location Omit for a pinned key
name, event_type, starts_at, ends_at yes Types as in section 2; times UTC; ends_at after starts_at
remote + address remote: true needs address_line1, city, state, postal_code A remote (Mobile) event lasts 7 days at most
guest_count no 1 to 5000
contact_name, contact_phone, customer_email, customer_notes no The customer; shown on the event
tier, add_ons, signature_drinks, named_problems no Stored as given (a string and three lists) and sent back in every answer and webhook; Br1gade does not interpret them
quoted_price_cents no The quoted price, visible to Owners and Managers
needs no Staffing lines {role, headcount, start_at?, end_at?}; times default to the event’s; roles as in the app (BARTENDER, SERVER, …)

Response 201 (or 200 for a repeat): { "success": true, "event": {…} } with the event object below. A day that is BOOKED and no live hold_id → 409 CAPACITY_FULL.

The event object (every answer and every event.* webhook):

{
  "id": "…", "external_id": "quote_8841", "business_id": "…", "location_id": "…", "hold_id": "…",
  "status": "DRAFT", "source": "API",
  "name": "Smith wedding", "event_type": "WEDDING", "starts_at": "…Z", "ends_at": "…Z",
  "remote": true, "address": { "line1": "12 Garden Lane", "line2": null, "city": "Little Rock", "state": "AR", "postal_code": "72201", "country": "US" },
  "guest_count": 120, "tier": "black_tie", "add_ons": ["champagne_toast"], "signature_drinks": ["Garden Spritz"], "named_problems": [],
  "contact_name": "Jane Smith", "contact_phone": "501-555-0123", "customer_email": "jane@example.com", "customer_notes": "…",
  "quoted_price_cents": 250000,
  "needs": [{ "id": "…", "role": "BARTENDER", "headcount": 2, "start_at": "…Z", "end_at": "…Z" }],
  "crew_assigned": 0,
  "actuals": null, "closed_at": null, "cancel_reason": null, "posted_at": null,
  "created_at": "…", "modified_at": "…"
}

status is DRAFT (waiting for the manager), POSTED (staffed and on the schedule), COMPLETED (closed with actuals, section 11) or CANCELLED. crew_assigned is how many shifts have a person on them. No names, timesheets or checklists ever leave Br1gade.

Call Does
GET /v1/events/<id> One event
GET /v1/events?external_id=quote_8841 The event with your id (events has one or none)
GET /v1/events?location_id=&from=&to=&status=&limit=&cursor= A page of events ordered by start, limit up to 100; from/to are local dates; answer carries next_cursor until the last page
PATCH /v1/events/<id> Any of starts_at, ends_at, remote and the address, guest_count, name, the contact fields, customer_notes, tier, add_ons, signature_drinks, named_problems, quoted_price_cents, needs. Moving to another day re-checks that day’s capacity. Once posted, times, staffing and remote are fixed (409 EVENT_POSTED)
DELETE /v1/events/<id> with { "reason": "…" } Cancels the event and every shift on it, and tells the people on them

11. Closing an event

When the event has ended, an Owner or Manager closes it in the app (the event › Close event) with the actuals: hours worked, bartenders, add-ons that were served, leftover count, and whether there was an incident. Hours and bartenders are prefilled from the clock-ins on the event’s shifts. The event becomes COMPLETED and event.closed goes out with:

"actuals": { "hours_actual": 11.5, "bartenders_actual": 2, "add_ons_actual": ["champagne_toast"], "leftover_count": 3, "incident_report": false, "closed_at": "…Z" }

A closed event cannot be changed or cancelled through the API (409 EVENT_COMPLETED). Who worked, their timesheets and checklists stay in Br1gade.