Skip to main content

Blackleaf Reporting API: Date Windows, Locations and Pagination

Use GET /analytics/reports to read Blackleaf reporting aggregates for an authenticated workspace or a selected location. Define the reporting period and entity, retrieve every cursor page, and keep the extraction time with the result. This is a read workflow for an application dashboard, a reporting export or a comparison your team has defined.

The current API reference documents the request and response envelope. Available report frames, entities and metric fields depend on the records available to your workspace. This guide does not establish that every dashboard metric, source system or store is covered by this endpoint.

The examples are inert documentation and have not been executed. They contain illustrative dates and a fictional location identifier. No private report data was accessed for this article. The cover is a conceptual API workflow illustration, not a reporting screen or measured result.

Choose the reporting question before the query

A useful reporting integration starts with a defined question and a repeatable extraction rule. Agree on the included stores, reporting interval, entity and metric definition before building charts.

Use caseRead designInterpretation check
A daily operating summaryRead daily top totals for an explicit completed period.Confirm which metrics are present and whether late corrections can change the period.
A comparison between locationsRun a separate, consistently filtered extraction for each authorized location.Use the same period and accounting basis. Do not add a workspace total to its location totals.
An entity-level trendUse the supported entity type and its actual identifier.Confirm the entity exists in the selected scope; an empty response does not establish no business activity.

This endpoint is separate from the messaging Performance dashboard and the monthly billing workbook and payment receipts. Do not assume those products share every field, calculation or export format.

Set a fixed reporting period

keyType is required. The reference gives hour, day, week, month and year as interval examples; use a frame actually stored for your workspace. Daily keys use a zero-padded date such as 2026-10-01. Other frames need their own stored key format.

For one period, supply key. For a range, use keyFrom and keyTo. The bounds are inclusive. Do not combine an exact key with either range parameter. Preserve the stored zero-padded format because the bounds compare period-key strings.

When all three period filters are omitted, the current implementation starts from today in America/Los_Angeles and goes backward by default. Hourly queries include all hours of that day. A continuation preserves that original default date across midnight, but it does not freeze the report values. Explicit periods are easier to reproduce than a rolling default.

For a month-end read, save the chosen first and last dates, interval, sort and extraction time. For comparisons, use equivalent completed windows and disclose missing data or later corrections. A report total alone does not establish what caused a change.

Read workspace totals or one location

Bearer authentication selects the workspace scope. Keep the API token on a trusted backend and out of browser code, public examples and logs. The request does not accept an org parameter to choose another workspace.

Omit locationId for organization-wide reports. Add the actual authorized location identifier to read that location’s stored aggregates. Keep the same location on every continuation request. An invalid location query does not fall back to workspace totals.

subKey and subKeyType filter the reporting entity within that scope. Each defaults independently to top when omitted. Selecting a location and selecting an entity are separate choices; one does not replace the other.

This illustrative request reads daily totals for a fixed month. It has not been sent:

curl --get "https://api.blackleaf.io/analytics/reports" \
  --header "Authorization: Bearer $BLACKLEAF_API_KEY" \
  --data-urlencode "keyType=day" \
  --data-urlencode "subKey=top" \
  --data-urlencode "subKeyType=top" \
  --data-urlencode "keyFrom=2026-10-01" \
  --data-urlencode "keyTo=2026-10-31" \
  --data-urlencode "pageSize=50" \
  --data-urlencode "sort=asc"

For a location extraction, include locationId=example-location-id after replacing that fictional identifier with the intended location. Confirm access and available records through your normal authorized process.

Continue with the returned cursor

The response contains items, pageSize, hasMoreItems and nextCursor. Results are combined report groups, rather than a count of raw source records. The default page size is 100; the documented range is 1 to 500.

  1. Start without a cursor and retain the complete query definition.
  2. Process that response’s items and save the extraction progress.
  3. When more results remain, pass the returned nextCursor as cursor, keeping the same filters, location and sort.
  4. Finish when hasMoreItems is false and nextCursor is null.

Treat the cursor as opaque. Do not decode it to build your own continuation or reuse it for another workspace, location or filter set. It is not an authorization credential. This endpoint does not provide numbered-page access or a totalItems count.

For example, the following location continuation retains the original query and adds only the returned cursor. The placeholder is not a working cursor and the request has not been executed:

curl --get "https://api.blackleaf.io/analytics/reports" \
  --header "Authorization: Bearer $BLACKLEAF_API_KEY" \
  --data-urlencode "keyType=day" \
  --data-urlencode "locationId=example-location-id" \
  --data-urlencode "subKey=top" \
  --data-urlencode "subKeyType=top" \
  --data-urlencode "keyFrom=2026-10-01" \
  --data-urlencode "keyTo=2026-10-31" \
  --data-urlencode "pageSize=50" \
  --data-urlencode "sort=asc" \
  --data-urlencode "cursor=CURSOR_FROM_PREVIOUS_RESPONSE"

Obtain that cursor from a first location request with the same parameters and no cursor. The earlier workspace-wide example cannot supply a cursor for this location query.

Interpret merged results without double counting

The reviewed merger combines entries that match all six grouping fields: subKeyType, subKey, key, keyType, parentKey and parentKeyType. It applies different rules to numbers, timestamps and descriptive values within each group.

Field or valueHow the reviewed merger combines it
Finite numeric metrics, such as total or invoiceAdd the values within the group, except for the grouping fields, the timestamp rules below and fields whose names start with eod.
Fields whose names start with eodCarry them as snapshot metadata rather than adding them. When supplied values conflict, prefer the more recently modified report entry.
Creation and first-transaction timestampsUse the earliest dateCreated Date value, numeric dateCreatedLong or numeric transactionFirst, respectively.
Modification and last-transaction timestampsUse the latest dateModified Date value, numeric dateModifiedLong or numeric transactionLast, respectively.
An average object and other descriptive valuesCarry the supplied metadata. Conflicts prefer the more recently modified entry; a field omitted by that entry can remain from an earlier one. An object is carried as a whole, not recalculated as a weighted average.

For an illustrative group with numeric total values of 10 and 20, the combined total is 30. If its eodBalance values are 50 and 80 and the entry supplying 80 was modified later, the snapshot value is 80, not 130. These numbers are fictional explanations, not an API result.

The value’s type matters. An object-valued average is metadata; a finite number under that name follows the numeric rule. A field name alone does not establish a valid business calculation. Confirm the returned field types, units and underlying weights before using an average or combining results across periods.

Do not sum every returned number indiscriminately. Confirm the meaning, units and scope of each field used by your application. In particular, do not average already-calculated averages without their appropriate underlying weights, or combine daily and monthly aggregates as if they were independent events.

Paging reads live aggregates, not an immutable snapshot. Values can change during an extraction, and new rows that belong before the cursor require a fresh first-page read. For a repeatable export, retain the query, extraction time and reconciliation rule, then use an authorized reread to account for later changes. A saved cursor position alone does not make an export complete or final.

A reporting aggregate is also different from a message-level delivery record or an incremental marketing result. The API developer guide explains messaging records and events. The SMS holdout guide explains the separate comparison needed to estimate a campaign’s contribution.

Handle empty results and read failures

An empty items array can be a successful HTTP 200 response. Check the selected period, frame, entity and location before interpreting it as zero activity. Preserve missing or unavailable data as such.

ResultNext check
400: invalid query or cursorCheck exact parameter names, non-empty string values, period rules and the original cursor scope. Unknown parameters such as page and org are rejected.
401 or 403Review the configured credentials and authorized workspace through the normal access process. Do not expose the token in an error report.
503: REPORT_INDEX_REQUIREDThe reviewed read path could not use a required reporting index. Keep the extraction incomplete and provide the sanitized code to support. Repeated requests or a different API key do not provision the missing index; deployment work belongs to the service owner.
503: REPORT_UNAVAILABLEThe report read failed for another reason handled as unavailable. Preserve the sanitized code and the incomplete attempt. Use your application’s bounded retry policy and contact support if it persists; do not turn the failed read into a zero.
504: REPORT_TIMEOUTThe report query exceeded its allowed time. Narrow the period or entity scope before a controlled retry. A narrower query is a different extraction; record that change. A timeout does not establish an empty report.

Keep the last complete export separate from an incomplete attempt. For help, provide the method, path, filters, approximate UTC time, HTTP status and sanitized error. Exclude API keys and unnecessary customer-level report contents.

Frequently asked questions

Does this reporting request send messages or change contacts?

GET /analytics/reports reads reporting aggregates. Message sends, contact changes, campaign actions and automation invocations are separate operations. This article executes none of them.

Can I use the messaging sandbox as a copy of all reporting data?

The messaging sandbox covers supported messaging simulation. It is not a promise of a separate, isolated reporting dataset or of simulated behavior for every API resource.

Does a cursor guarantee a consistent snapshot?

No. It continues the scoped query through live aggregates. Save the extraction definition and time, and reconcile later changes when your use case requires it.

Does an aggregate prove that a text caused a sale?

No. A total, trend or attributed purchase does not by itself establish causation. Use a suitable comparison design and keep the reporting scope and metric definitions attached to the readout.

Plan one reporting integration

Reviewed November 3, 2026 against the current public API reference, version 2.8.4, and the reporting query and merge implementation. Local implementation and public documentation do not establish account-specific data availability or production response contents. Check the current operation schema when implementing.

Bring one reporting question, the intended workspace and locations, the required period, metric definitions and export destination to an API integration review. Agree on how to recognize a complete read and reconcile changes before relying on the result.

Blackleaf dispensary texting remains available with supported existing POS systems without a POS switch. Reporting coverage should be confirmed for the source data and workflow you actually use.