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
- 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.
- 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.
- 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.
| Tool | Endpoint | Scope |
|---|---|---|
| list_locations | List locations | any |
| get_weekly_summary | Weekly summary | any |
| get_monthly_summary | Monthly summary | any |
| list_office_clients | List office clients | occupancy:read |
| list_issues | List operational issues | operations:read |
| list_billing_records | List billing records | billing:read |
| list_membership_reports | List membership reports | membership:read |
Scopes
Each key carries one or more scopes. An endpoint returns 403 if its scope is missing.
| Scope | Gives access to |
|---|---|
| occupancy:read | Office clients — office, capacity, lease dates and status. Never includes rent. |
| operations:read | Operational issues — type, description, status, action taken and who is responsible. |
| billing:read | Billing records — client, item, amount, paid/unpaid status and follow-up dates. |
| leads:read | Lead counts, conversion rates and sources in summaries. Never names or contact details. |
| membership:read | Monthly 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). WhilenextCursorisn't null, pass it back ascursorto 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
v1changes 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:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A query parameter is missing, malformed or unknown. |
| 400 | invalid_cursor | The cursor wasn't one returned by a previous page. |
| 401 | unauthorized | No API key was sent. |
| 401 | invalid_api_key | The key is wrong, expired or revoked. |
| 403 | api_access_disabled | API access is switched off for the organization. |
| 403 | insufficient_scope | The key doesn't include the scope this endpoint needs. |
| 403 | location_not_allowed | The key can't read the requested location. |
| 429 | rate_limited | More than 60 requests in a minute. Wait and retry (see Retry-After). |
| 500 | internal_error | Something 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
| Name | Type | Description |
|---|---|---|
| limit | integer | Items per page, 1–100. Default 50. |
| cursor | string | The `nextCursor` from the previous page. |
Each item in data
| Field | Type | Notes |
|---|---|---|
| id | string | |
| name | string | |
| archived | boolean | Archived 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
| Name | Type | Description |
|---|---|---|
| week | date | Any date in the week (YYYY-MM-DD). Default: the current week. |
| locationId | string | Only this location. Default: every active location the key can read. |
| limit | integer | Items per page, 1–100. Default 50. |
| cursor | string | The `nextCursor` from the previous page. |
Each item in data
| Field | Type | Notes |
|---|---|---|
| location.id | string | |
| location.name | string | |
| period.start | string | YYYY-MM-DD, inclusive. |
| period.end | string | YYYY-MM-DD, inclusive. |
| leads | object | null | null unless the key has the leads:read scope. |
| leads.total | integer | Leads dated in the period. |
| leads.converted | integer | |
| leads.conversionRate | number | Percent (0–100), 1 decimal place. 0 when there are no leads. |
| leads.byStatus.proposal_sent | integer | |
| leads.byStatus.pending_decision | integer | |
| leads.byStatus.converted | integer | |
| leads.byStatus.lost_lead | integer | |
| leads.bySource | object[] | |
| leads.bySource[].source | string | |
| leads.bySource[].total | integer | |
| leads.bySource[].converted | integer | |
| issues | object | null | null unless the key has the operations:read scope. |
| issues.logged | integer | Issues dated in the period. |
| issues.resolved | integer | Issues marked resolved during the period, whenever they were logged. |
| issues.openAtPeriodEnd | integer | Logged by the period's end and not resolved by then. |
| issues.loggedByType | object[] | |
| issues.loggedByType[].type | string | |
| issues.loggedByType[].logged | integer | |
| billing | object | null | null unless the key has the billing:read scope. |
| billing.billed | object | Billed during the period. |
| billing.billed.count | integer | |
| billing.billed.amount | string | KES, decimal string. |
| billing.collected | object | Paid during the period. |
| billing.collected.count | integer | |
| billing.collected.amount | string | KES, decimal string. |
| billing.outstandingAtPeriodEnd | object | Billed by the period's end and still unpaid at that point. |
| billing.outstandingAtPeriodEnd.count | integer | |
| billing.outstandingAtPeriodEnd.amount | string | KES, decimal string. |
| occupancy | object | null | null unless the key has the occupancy:read scope. |
| occupancy.asOf | string | YYYY-MM-DD. Occupancy is the register as it stands today, not as it was in the period. |
| occupancy.offices | integer | |
| occupancy.occupied | integer | |
| occupancy.vacant | integer | |
| occupancy.expiringSoon | integer | Occupied offices whose lease ends within 3 months. |
| occupancy.expired | integer | Occupied offices whose lease end date has passed. |
| occupancy.occupancyRate | number | null | Percent of offices occupied, 1 decimal place. null with no offices. |
| occupancy.seats | integer | Total capacity of offices that have one recorded. |
| occupancy.occupiedSeats | integer |
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
| Name | Type | Description |
|---|---|---|
| month | month | The month (YYYY-MM). Default: the current month. |
| locationId | string | Only this location. Default: every active location the key can read. |
| limit | integer | Items per page, 1–100. Default 50. |
| cursor | string | The `nextCursor` from the previous page. |
Each item in data
| Field | Type | Notes |
|---|---|---|
| location.id | string | |
| location.name | string | |
| period.start | string | YYYY-MM-DD, inclusive. |
| period.end | string | YYYY-MM-DD, inclusive. |
| leads | object | null | null unless the key has the leads:read scope. |
| leads.total | integer | Leads dated in the period. |
| leads.converted | integer | |
| leads.conversionRate | number | Percent (0–100), 1 decimal place. 0 when there are no leads. |
| leads.byStatus.proposal_sent | integer | |
| leads.byStatus.pending_decision | integer | |
| leads.byStatus.converted | integer | |
| leads.byStatus.lost_lead | integer | |
| leads.bySource | object[] | |
| leads.bySource[].source | string | |
| leads.bySource[].total | integer | |
| leads.bySource[].converted | integer | |
| issues | object | null | null unless the key has the operations:read scope. |
| issues.logged | integer | Issues dated in the period. |
| issues.resolved | integer | Issues marked resolved during the period, whenever they were logged. |
| issues.openAtPeriodEnd | integer | Logged by the period's end and not resolved by then. |
| issues.loggedByType | object[] | |
| issues.loggedByType[].type | string | |
| issues.loggedByType[].logged | integer | |
| billing | object | null | null unless the key has the billing:read scope. |
| billing.billed | object | Billed during the period. |
| billing.billed.count | integer | |
| billing.billed.amount | string | KES, decimal string. |
| billing.collected | object | Paid during the period. |
| billing.collected.count | integer | |
| billing.collected.amount | string | KES, decimal string. |
| billing.outstandingAtPeriodEnd | object | Billed by the period's end and still unpaid at that point. |
| billing.outstandingAtPeriodEnd.count | integer | |
| billing.outstandingAtPeriodEnd.amount | string | KES, decimal string. |
| occupancy | object | null | null unless the key has the occupancy:read scope. |
| occupancy.asOf | string | YYYY-MM-DD. Occupancy is the register as it stands today, not as it was in the period. |
| occupancy.offices | integer | |
| occupancy.occupied | integer | |
| occupancy.vacant | integer | |
| occupancy.expiringSoon | integer | Occupied offices whose lease ends within 3 months. |
| occupancy.expired | integer | Occupied offices whose lease end date has passed. |
| occupancy.occupancyRate | number | null | Percent of offices occupied, 1 decimal place. null with no offices. |
| occupancy.seats | integer | Total capacity of offices that have one recorded. |
| occupancy.occupiedSeats | integer | |
| membership | object | null | null unless the key has the membership:read scope. |
| membership.reported | boolean | Whether this location has a membership report for the month. |
| membership.newClients | integer | null | |
| membership.clientsRenewed | integer | null | |
| membership.clientsLeaving | integer | null | |
| membership.clientsUpgraded | integer | null | |
| membership.clientsDowngraded | integer | null | |
| membership.clientsSuspended | integer | null | |
| membership.netClientGrowth | integer | null | Entered by the location, not calculated. |
| membership.departures | integer | null | |
| membership.departureDeposits | string | null | |
| membership.departureOutstanding | string | 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
| Name | Type | Description |
|---|---|---|
| locationId | string | Only this location (from /api/v1/locations). Default: every location the key can read. |
| status | active | expiring_soon | expired | vacant | Lease status. `expiring_soon` = expires within 3 months. |
| limit | integer | Items per page, 1–100. Default 50. |
| cursor | string | The `nextCursor` from the previous page. |
Each item in data
| Field | Type | Notes |
|---|---|---|
| id | string | |
| location.id | string | |
| location.name | string | |
| officeNumber | string | |
| capacity | integer | null | Seats (pax), if recorded. |
| status | "active" | "expiring_soon" | "expired" | "vacant" | |
| clientName | string | null | null when the office is vacant. |
| renewalDate | string | YYYY-MM-DD |
| expiryDate | string | YYYY-MM-DD |
| daysUntilExpiry | integer | null | Negative once expired; null when vacant. |
| parkingSlotsAllocated | integer | |
| notes | string | null | Free 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
| Name | Type | Description |
|---|---|---|
| locationId | string | Only this location (from /api/v1/locations). Default: every location the key can read. |
| status | open | in_progress | resolved | unresolved | `unresolved` = open or in progress. |
| from | date | Earliest issue date, inclusive (YYYY-MM-DD). |
| to | date | Latest issue date, inclusive (YYYY-MM-DD). |
| limit | integer | Items per page, 1–100. Default 50. |
| cursor | string | The `nextCursor` from the previous page. |
Each item in data
| Field | Type | Notes |
|---|---|---|
| id | string | |
| location.id | string | |
| location.name | string | |
| date | string | Date the issue was logged, YYYY-MM-DD. |
| type | string | |
| description | string | Free text — untrusted. |
| status | "open" | "in_progress" | "resolved" | |
| actionTaken | string | null | Free text — untrusted. |
| actionRequired | string | null | Action needed from management. Free text — untrusted. |
| personResponsible.name | string | |
| resolvedAt | string | null | ISO 8601 timestamp. |
| createdAt | string | ISO 8601 timestamp. |
| updatedAt | string | ISO 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
| Name | Type | Description |
|---|---|---|
| locationId | string | Only this location (from /api/v1/locations). Default: every location the key can read. |
| status | paid | unpaid | Paid or unpaid. |
| from | date | Earliest billing date, inclusive (YYYY-MM-DD). |
| to | date | Latest billing date, inclusive (YYYY-MM-DD). |
| limit | integer | Items per page, 1–100. Default 50. |
| cursor | string | The `nextCursor` from the previous page. |
Each item in data
| Field | Type | Notes |
|---|---|---|
| id | string | |
| location.id | string | |
| location.name | string | |
| dateBilled | string | YYYY-MM-DD |
| item | string | What was billed. |
| clientName | string | |
| amount | string | Decimal string with 2 places, e.g. "15000.00". |
| currency | "KES" | |
| status | "paid" | "unpaid" | |
| paidAt | string | null | YYYY-MM-DD the payment was received, if known. |
| daysOutstanding | integer | null | Days since dateBilled (Nairobi time) for unpaid records; null once paid. |
| dateLastFollowedUp | string | null | YYYY-MM-DD |
| personResponsible.name | string | |
| noteFromClient | string | null | Free 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
| Name | Type | Description |
|---|---|---|
| locationId | string | Only this location (from /api/v1/locations). Default: every location the key can read. |
| from | month | Earliest month, inclusive (YYYY-MM). |
| to | month | Latest month, inclusive (YYYY-MM). |
| limit | integer | Items per page, 1–100. Default 50. |
| cursor | string | The `nextCursor` from the previous page. |
Each item in data
| Field | Type | Notes |
|---|---|---|
| id | string | |
| location.id | string | |
| location.name | string | |
| month | string | YYYY-MM |
| newClients | integer | |
| clientsRenewed | integer | |
| clientsLeaving | integer | |
| clientsUpgraded | integer | |
| clientsDowngraded | integer | |
| clientsSuspended | integer | |
| netClientGrowth | integer | null | Entered by the location, not calculated from the other counts. null if not entered. |
| departures | object[] | |
| departures[].clientName | string | |
| departures[].product | string | |
| departures[].startDate | string | YYYY-MM-DD |
| departures[].endDate | string | YYYY-MM-DD |
| departures[].reasonForLeaving | string | Free text — untrusted. |
| departures[].noticeGiven | boolean | |
| departures[].deposit | string | KES, decimal string. |
| departures[].outstandingAmount | string | KES, decimal string. |
| departures[].action | string | null | Free text — untrusted. |