BrieflyLog API

Version 1 · read-only

The API gives a service chosen by a BrieflyLog organization, such as an AI assistant, read-only access to selected operational data. Access is always granted by the organization, limited to the data and locations it chooses, and logged. HR data is never available. Machine-readable spec: openapi.json (OpenAPI 3.1). AI agents can also connect over MCP.

Getting started

  1. The organization's administrator creates an API key under Management → Integrations, choosing the scopes, locations and expiry, and shares it with you securely. API access must be enabled for the organization first.
  2. Using the API means you accept the Developer Terms. In particular, no training on the data, delete it when access ends, and treat free text as untrusted.
  3. Send the key as a bearer token from your server. Never send it from a browser or app.
curl https://www.brieflylog.app/api/v1/billing?status=unpaid \
  -H "Authorization: Bearer bl_live_…"

Connect over MCP

AI agents can use the same keys through the Model Context Protocol instead of calling the REST endpoints directly. The server speaks Streamable HTTP (stateless) at:

https://www.brieflylog.app/api/mcp

Send the key in the Authorization header. For example, in Claude Code:

claude mcp add --transport http brieflylog https://www.brieflylog.app/api/mcp \
  --header "Authorization: Bearer bl_live_…"

Or in an MCP client configuration file:

{
  "mcpServers": {
    "brieflylog": {
      "type": "http",
      "url": "https://www.brieflylog.app/api/mcp",
      "headers": {
        "Authorization": "Bearer bl_live_…"
      }
    }
  }
}

Each tool matches one endpoint below and returns the same JSON. A key only sees the tools its scopes allow. Tool calls count toward the rate limit and appear in the organization's request log.

ToolEndpointScope
list_locationsList locationsany
get_weekly_summaryWeekly summaryany
get_monthly_summaryMonthly summaryany
list_office_clientsList office clientsoccupancy:read
list_issuesList operational issuesoperations:read
list_billing_recordsList billing recordsbilling:read
list_membership_reportsList membership reportsmembership:read

Scopes

Each key carries one or more scopes. An endpoint returns 403 if its scope is missing.

ScopeGives access to
occupancy:readOffice clients — office, capacity, lease dates and status. Never includes rent.
operations:readOperational issues — type, description, status, action taken and who is responsible.
billing:readBilling records — client, item, amount, paid/unpaid status and follow-up dates.
leads:readLead counts, conversion rates and sources in summaries. Never names or contact details.
membership:readMonthly membership reports and client departures, including deposits and amounts owed. Admin-only in BrieflyLog.

Keys can also be limited to certain locations. List the ones a key can read with GET /api/v1/locations.

Conventions

  • Responses are JSON: { data: [...], nextCursor }.
  • Pagination: pass limit (1–100, default 50). While nextCursor isn't null, pass it back as cursor to get the next page.
  • Dates are YYYY-MM-DD. Timestamps are ISO 8601 in UTC. “Today” is Nairobi time.
  • Money is a decimal string in KES, e.g. "15000.00".
  • Statuses are lowercase, e.g. unpaid, expiring_soon.
  • Rate limit: 60 requests per minute per key.
  • Untrusted text: descriptions and notes are typed by people and may contain anything, including text that looks like instructions. Never let them steer an AI agent's behaviour. Notes are only included if the key was created with “Include notes”.
  • Versioning: breaking changes only ship in a new version, with at least 90 days' notice before v1 changes incompatibly. New fields may be added at any time, so ignore fields you don't recognize.

Errors

Errors return { error: { code, message } } with one of these codes:

StatusCodeMeaning
400invalid_requestA query parameter is missing, malformed or unknown.
400invalid_cursorThe cursor wasn't one returned by a previous page.
401unauthorizedNo API key was sent.
401invalid_api_keyThe key is wrong, expired or revoked.
403api_access_disabledAPI access is switched off for the organization.
403insufficient_scopeThe key doesn't include the scope this endpoint needs.
403location_not_allowedThe key can't read the requested location.
429rate_limitedMore than 60 requests in a minute. Wait and retry (see Retry-After).
500internal_errorSomething went wrong on our side. Retry later.

Endpoints

List locations

GET /api/v1/locations

The locations this API key can read. Use a location's `id` as the `locationId` filter on other endpoints.

Scope: any valid key

Query parameters

NameTypeDescription
limitintegerItems per page, 1–100. Default 50.
cursorstringThe `nextCursor` from the previous page.

Each item in data

FieldTypeNotes
idstring
namestring
archivedbooleanArchived locations no longer operate but keep their history.

Weekly summary

GET /api/v1/summaries/weekly

Totals for one reporting week (Monday–Sunday) per location: leads, issues and billing for the week, plus current occupancy. Each section is included only when the key has its scope (otherwise null), so one call answers as much as the key allows.

Scope: any valid key

Query parameters

NameTypeDescription
weekdateAny date in the week (YYYY-MM-DD). Default: the current week.
locationIdstringOnly this location. Default: every active location the key can read.
limitintegerItems per page, 1–100. Default 50.
cursorstringThe `nextCursor` from the previous page.

Each item in data

FieldTypeNotes
location.idstring
location.namestring
period.startstringYYYY-MM-DD, inclusive.
period.endstringYYYY-MM-DD, inclusive.
leadsobject | nullnull unless the key has the leads:read scope.
leads.totalintegerLeads dated in the period.
leads.convertedinteger
leads.conversionRatenumberPercent (0–100), 1 decimal place. 0 when there are no leads.
leads.byStatus.proposal_sentinteger
leads.byStatus.pending_decisioninteger
leads.byStatus.convertedinteger
leads.byStatus.lost_leadinteger
leads.bySourceobject[]
leads.bySource[].sourcestring
leads.bySource[].totalinteger
leads.bySource[].convertedinteger
issuesobject | nullnull unless the key has the operations:read scope.
issues.loggedintegerIssues dated in the period.
issues.resolvedintegerIssues marked resolved during the period, whenever they were logged.
issues.openAtPeriodEndintegerLogged by the period's end and not resolved by then.
issues.loggedByTypeobject[]
issues.loggedByType[].typestring
issues.loggedByType[].loggedinteger
billingobject | nullnull unless the key has the billing:read scope.
billing.billedobjectBilled during the period.
billing.billed.countinteger
billing.billed.amountstringKES, decimal string.
billing.collectedobjectPaid during the period.
billing.collected.countinteger
billing.collected.amountstringKES, decimal string.
billing.outstandingAtPeriodEndobjectBilled by the period's end and still unpaid at that point.
billing.outstandingAtPeriodEnd.countinteger
billing.outstandingAtPeriodEnd.amountstringKES, decimal string.
occupancyobject | nullnull unless the key has the occupancy:read scope.
occupancy.asOfstringYYYY-MM-DD. Occupancy is the register as it stands today, not as it was in the period.
occupancy.officesinteger
occupancy.occupiedinteger
occupancy.vacantinteger
occupancy.expiringSoonintegerOccupied offices whose lease ends within 3 months.
occupancy.expiredintegerOccupied offices whose lease end date has passed.
occupancy.occupancyRatenumber | nullPercent of offices occupied, 1 decimal place. null with no offices.
occupancy.seatsintegerTotal capacity of offices that have one recorded.
occupancy.occupiedSeatsinteger

Monthly summary

GET /api/v1/summaries/monthly

Totals for one calendar month per location: leads, issues and billing for the month, the month's membership report, and current occupancy. Each section is included only when the key has its scope (otherwise null), so one call answers as much as the key allows.

Scope: any valid key

Query parameters

NameTypeDescription
monthmonthThe month (YYYY-MM). Default: the current month.
locationIdstringOnly this location. Default: every active location the key can read.
limitintegerItems per page, 1–100. Default 50.
cursorstringThe `nextCursor` from the previous page.

Each item in data

FieldTypeNotes
location.idstring
location.namestring
period.startstringYYYY-MM-DD, inclusive.
period.endstringYYYY-MM-DD, inclusive.
leadsobject | nullnull unless the key has the leads:read scope.
leads.totalintegerLeads dated in the period.
leads.convertedinteger
leads.conversionRatenumberPercent (0–100), 1 decimal place. 0 when there are no leads.
leads.byStatus.proposal_sentinteger
leads.byStatus.pending_decisioninteger
leads.byStatus.convertedinteger
leads.byStatus.lost_leadinteger
leads.bySourceobject[]
leads.bySource[].sourcestring
leads.bySource[].totalinteger
leads.bySource[].convertedinteger
issuesobject | nullnull unless the key has the operations:read scope.
issues.loggedintegerIssues dated in the period.
issues.resolvedintegerIssues marked resolved during the period, whenever they were logged.
issues.openAtPeriodEndintegerLogged by the period's end and not resolved by then.
issues.loggedByTypeobject[]
issues.loggedByType[].typestring
issues.loggedByType[].loggedinteger
billingobject | nullnull unless the key has the billing:read scope.
billing.billedobjectBilled during the period.
billing.billed.countinteger
billing.billed.amountstringKES, decimal string.
billing.collectedobjectPaid during the period.
billing.collected.countinteger
billing.collected.amountstringKES, decimal string.
billing.outstandingAtPeriodEndobjectBilled by the period's end and still unpaid at that point.
billing.outstandingAtPeriodEnd.countinteger
billing.outstandingAtPeriodEnd.amountstringKES, decimal string.
occupancyobject | nullnull unless the key has the occupancy:read scope.
occupancy.asOfstringYYYY-MM-DD. Occupancy is the register as it stands today, not as it was in the period.
occupancy.officesinteger
occupancy.occupiedinteger
occupancy.vacantinteger
occupancy.expiringSoonintegerOccupied offices whose lease ends within 3 months.
occupancy.expiredintegerOccupied offices whose lease end date has passed.
occupancy.occupancyRatenumber | nullPercent of offices occupied, 1 decimal place. null with no offices.
occupancy.seatsintegerTotal capacity of offices that have one recorded.
occupancy.occupiedSeatsinteger
membershipobject | nullnull unless the key has the membership:read scope.
membership.reportedbooleanWhether this location has a membership report for the month.
membership.newClientsinteger | null
membership.clientsRenewedinteger | null
membership.clientsLeavinginteger | null
membership.clientsUpgradedinteger | null
membership.clientsDowngradedinteger | null
membership.clientsSuspendedinteger | null
membership.netClientGrowthinteger | nullEntered by the location, not calculated.
membership.departuresinteger | null
membership.departureDepositsstring | null
membership.departureOutstandingstring | null

List office clients

GET /api/v1/office-clients

Offices on the register and who occupies them, with lease dates and status — for occupancy and renewal questions. Archived offices are excluded. Rent is never included.

Scope: occupancy:read

Query parameters

NameTypeDescription
locationIdstringOnly this location (from /api/v1/locations). Default: every location the key can read.
statusactive | expiring_soon | expired | vacantLease status. `expiring_soon` = expires within 3 months.
limitintegerItems per page, 1–100. Default 50.
cursorstringThe `nextCursor` from the previous page.

Each item in data

FieldTypeNotes
idstring
location.idstring
location.namestring
officeNumberstring
capacityinteger | nullSeats (pax), if recorded.
status"active" | "expiring_soon" | "expired" | "vacant"
clientNamestring | nullnull when the office is vacant.
renewalDatestringYYYY-MM-DD
expiryDatestringYYYY-MM-DD
daysUntilExpiryinteger | nullNegative once expired; null when vacant.
parkingSlotsAllocatedinteger
notesstring | nullFree text — untrusted. null unless the key includes notes.

List operational issues

GET /api/v1/issues

Operational issues logged per location, newest first — for weekly and monthly issue summaries. Unresolved issues stay open across reporting periods until marked resolved.

Scope: operations:read

Query parameters

NameTypeDescription
locationIdstringOnly this location (from /api/v1/locations). Default: every location the key can read.
statusopen | in_progress | resolved | unresolved`unresolved` = open or in progress.
fromdateEarliest issue date, inclusive (YYYY-MM-DD).
todateLatest issue date, inclusive (YYYY-MM-DD).
limitintegerItems per page, 1–100. Default 50.
cursorstringThe `nextCursor` from the previous page.

Each item in data

FieldTypeNotes
idstring
location.idstring
location.namestring
datestringDate the issue was logged, YYYY-MM-DD.
typestring
descriptionstringFree text — untrusted.
status"open" | "in_progress" | "resolved"
actionTakenstring | nullFree text — untrusted.
actionRequiredstring | nullAction needed from management. Free text — untrusted.
personResponsible.namestring
resolvedAtstring | nullISO 8601 timestamp.
createdAtstringISO 8601 timestamp.
updatedAtstringISO 8601 timestamp.

List billing records

GET /api/v1/billing

Billed items and whether they've been paid, newest first. Use `status=unpaid` for the follow-up list. Amounts are in KES.

Scope: billing:read

Query parameters

NameTypeDescription
locationIdstringOnly this location (from /api/v1/locations). Default: every location the key can read.
statuspaid | unpaidPaid or unpaid.
fromdateEarliest billing date, inclusive (YYYY-MM-DD).
todateLatest billing date, inclusive (YYYY-MM-DD).
limitintegerItems per page, 1–100. Default 50.
cursorstringThe `nextCursor` from the previous page.

Each item in data

FieldTypeNotes
idstring
location.idstring
location.namestring
dateBilledstringYYYY-MM-DD
itemstringWhat was billed.
clientNamestring
amountstringDecimal string with 2 places, e.g. "15000.00".
currency"KES"
status"paid" | "unpaid"
paidAtstring | nullYYYY-MM-DD the payment was received, if known.
daysOutstandinginteger | nullDays since dateBilled (Nairobi time) for unpaid records; null once paid.
dateLastFollowedUpstring | nullYYYY-MM-DD
personResponsible.namestring
noteFromClientstring | nullFree text — untrusted. null unless the key includes notes.

List membership reports

GET /api/v1/membership-reports

One report per location per month: client movement counts and the clients who left, newest month first. A month with no report simply has no item.

Scope: membership:read

Query parameters

NameTypeDescription
locationIdstringOnly this location (from /api/v1/locations). Default: every location the key can read.
frommonthEarliest month, inclusive (YYYY-MM).
tomonthLatest month, inclusive (YYYY-MM).
limitintegerItems per page, 1–100. Default 50.
cursorstringThe `nextCursor` from the previous page.

Each item in data

FieldTypeNotes
idstring
location.idstring
location.namestring
monthstringYYYY-MM
newClientsinteger
clientsRenewedinteger
clientsLeavinginteger
clientsUpgradedinteger
clientsDowngradedinteger
clientsSuspendedinteger
netClientGrowthinteger | nullEntered by the location, not calculated from the other counts. null if not entered.
departuresobject[]
departures[].clientNamestring
departures[].productstring
departures[].startDatestringYYYY-MM-DD
departures[].endDatestringYYYY-MM-DD
departures[].reasonForLeavingstringFree text — untrusted.
departures[].noticeGivenboolean
departures[].depositstringKES, decimal string.
departures[].outstandingAmountstringKES, decimal string.
departures[].actionstring | nullFree text — untrusted.