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.
