API Reference
Beacon provides a REST API for direct integration when an SDK is not suitable for your environment. The API is used internally by all Beacon SDKs.
Authentication
All ingestion API requests require an API key passed as a Bearer token in the Authorization header:
curl -X POST https://api.beacon.softagility.com/v1/events \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '[{
"event_id": "01912345-6789-7abc-def0-123456789abc",
"category": "reports",
"name": "report_exported",
"timestamp": "2026-03-20T14:30:00Z",
"actor_id": "user-123",
"product": "MyApp",
"product_version": "1.2.0",
"properties": { "format": "pdf", "row_count": "1500" }
}]'Base URL
https://api.beacon.softagility.com/v1Ingestion Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/events | Track one or more events (batch, up to 1000) |
POST | /v1/events/sessions | Start a new session |
POST | /v1/events/sessions/end | End an active session |
POST | /v1/events/exceptions | Report an exception with stack trace |
POST /v1/events
Send a JSON array of event objects. Per-request limits: max 1 MB total payload, max 1000 events per batch. Per-event limit: each event object must be ≤ 4 KB serialized JSON; oversize individual events are rejected at the per-event level (the batch as a whole still completes).
Required fields per event:
event_id— UUID (v7 recommended)category— String, max 128 charsname— String, max 256 charstimestamp— ISO 8601 datetimeactor_id— String, max 512 charsproduct— Your product’s slug (the value created on the Products page in the Beacon dashboard). Must match a registered product or the event is rejected withUNRECOGNIZED_PRODUCT. Max 256 chars.product_version— Your application’s version string. Max 128 chars. By default, unknown values are auto-registered on first sight. If strict version mode is enabled for the product, values that aren’t pre-registered (or whose registered version is archived) are rejected withUNREGISTERED_VERSIONand the rejected events are permanently dropped — the SDK does not retry.
Optional fields:
properties— Flat key/value object (no nested objects or arrays). Max 20 keys; key max 64 chars; value max 256 chars. Reserved keys —account_id,accountId,account,license_id,licenseId,licensemay NOT appear insideproperties. Events that include any of these in properties are dead-lettered withreserved_property_key. Use the top-level fields below instead.session_id— UUID linking event to a session.account_id— Top-level customer-account identifier. 1-256 chars, no control characters. Accepted and stored on every plan; visibility is gated to Business and Enterprise. Empty / whitespace-only / oversized values are dead-lettered withinvalid_account_id.license_id— Top-level license / entitlement identifier. 1-256 chars, no control characters. Accepted on every plan; same gating asaccount_id. Empty / whitespace-only / oversized values are dead-lettered withinvalid_license_id. Prefer per-contract IDs over per-user IDs (see Licenses → Modeling licenses).schema_version— Optional caller-provided schema version. Recorded but not validated.
Response codes:
200— every event in the batch was accepted.207— at least one event-level rejection occurred (including the all-rejected case). The response body lists per-event acceptance/rejection results so the caller can identify which failed.400— request itself was malformed (not a JSON array, empty array, or batch exceeding the 1000-event-count limit).401— invalid or missing API key.402— ingestion is blocked for the tenant’s billing state. See 402 behavior below.413— request body exceeded the 1 MB transport limit.429— rate limit exceeded. Includes aRetry-Afterheader indicating how long to wait.
POST /v1/events/sessions
Start a new session.
Required fields: session_id, actor_id, product, product_version, started_at
Optional fields:
properties— flat key/value object, same property limits as events (max 20 keys; key max 64 chars; value max 256 chars). Same reserved-key list as event ingestion.account_id,license_id— top-level optional customer-account / license identifiers (1-256 chars). Captured at session start and carried on the session row; mid-session SDK calls toSetAccount/SetLicensedo not mutate the existing session row. Sessions return422on validation failures rather than dead-lettering.
Response: 200 with { "session_id": "...", "status": "accepted" }.
POST /v1/events/sessions/end
End an active session.
Required fields: session_id, ended_at.
Optional fields: end_reason — defaults to "normal" if omitted. Valid client values: "normal" (graceful shutdown) and "sdk_recovery" (the SDK detected a previously-orphaned session and is closing it). The value "timeout" is reserved for server-side timeout marking and is rejected if sent by a client.
POST /v1/events/exceptions
Report an exception.
Required fields: exception_id, exception_type, severity (fatal or non_fatal), occurred_at, actor_id, product, product_version.
Optional fields:
message— recorded if ≤ 1000 chars; truncated to 1000 chars if longer (not rejected).stack_trace— recorded if ≤ 32,768 chars; truncated to 32,768 chars if longer.breadcrumbs— any non-null JSON value (typically an array of crumb objects). Beacon does not enforce a specific shape here; see the SDK guides for the convention each SDK uses.session_id— links the exception to a session if both exist. An invalid or unknown UUID is silently ignored rather than rejected.account_id,license_id— top-level optional identifiers (1-256 chars). Validation failures return422.environment_context— application/runtime context (OS, version, etc.) typically populated automatically by the SDK.
Response: 200 with { "exception_id": "...", "status": "accepted", "fingerprint": "..." }.
Rate Limits
Rate limiting applies to POST /v1/events only. Sessions and exceptions endpoints are not currently rate-limited.
Limits are per-minute and configured per plan:
| Plan | Requests per minute |
|---|---|
| Trial | 30 |
| Starter | 120 |
| Pro | 600 |
| Business | 3000 |
| Enterprise | 6000 |
When the limit is exceeded, the API returns 429 with a Retry-After header indicating how many seconds to wait before retrying. (No X-RateLimit-* headers are emitted today; consult Retry-After only.)
402 behavior
402 Payment Required is returned when the tenant’s billing state blocks ingestion. Three triggering conditions:
- Hard cap reached — the tenant has exhausted its monthly event allotment AND its plan policy is to hard-cap on overage (Trial and Starter, where overage is not billable). On charge-policy plans (Pro, Business, Enterprise), exceeding the included allotment generates overage billing instead of a hard cap.
- Trial expired — the 21-day trial has ended without conversion to a paid plan.
- Subscription cancelled — the subscription was cancelled and the grace period has elapsed.
Cap-policy plans hard-cap only after their grace period. Recovery paths: upgrade to a paid plan, reactivate a cancelled subscription, or wait for the next billing cycle (cap-policy plans only). SDKs queue events to local persistent storage when they receive 402 and retry automatically once the state clears.
Version registrations
POST /v1/version-registrations registers a (product, version) pair so that ingestion can accept events for that version when strict version mode is enabled. Requires the manage_products permission. When strict mode is off (the default), this endpoint is optional — versions are auto-registered on first ingestion.
Required fields:
product— Product slug.product_version— Version string. Max 128 chars.
Optional fields:
release_date— ISO 8601 date (YYYY-MM-DD) the version was released to users. Captured today; consumed by the version-selector “Latest” tie-breaker and reserved for future release-window analytics. May be set tonull(or omitted).
Success response: 201 Created
{
"id": "01912345-...",
"product": "my-product",
"product_version": "1.2.0",
"release_date": "2026-05-01",
"created_at": "2026-05-10T14:30:00Z"
}Error responses:
400 validation_error— missing or malformed required field.403 forbidden— caller lacksmanage_products.404 product_not_found—productdoesn’t match any product in the tenant.409 product_deleting— the target product is in thedeletinglifecycle state and won’t accept new versions.409 version_already_registered— the(product, version)pair already exists. Use the version update endpoint below to changerelease_dateorstatusrather than re-posting.
PATCH /v1/products/{productId}/versions/{versionId}
Updates an existing version. Used for editing release_date and for archiving/unarchiving (the canonical “delete” semantic — there is no hard-delete endpoint).
Optional PATCH fields:
release_date— set or change the release date. Passingnull(or omitting the field) leaves the existing value unchanged — there is no explicit clear semantic today.status—"active"or"archived". Archived versions are rejected by strict-mode ingestion (UNREGISTERED_VERSION); flipping back toactiveresumes ingestion.
Requires manage_products.
For the user-facing flow, see Register a version and Strict version mode.
Account and License Endpoints
Available on Business and Enterprise plans (Trial also during evaluation). All require the view_accounts permission. A non-eligible plan returns 402 plan_limit_reached with the standard plan-gate message.
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/accounts | Paginated list of accounts observed under the tenant. |
GET | /v1/accounts/{accountId} | One account’s overview (counts, products, versions, licenses, recent exceptions). |
GET | /v1/accounts/{accountId}/features | Top (category, name) event pairs for the account. |
GET | /v1/accounts/{accountId}/actors | Paginated actors observed under the account. |
GET | /v1/accounts/{accountId}/licenses | Licenses observed under the account. |
GET | /v1/accounts/{accountId}/versions | (source_app, source_version) rows with usage counts. |
GET | /v1/accounts/{accountId}/exceptions | Exceptions for the account (same response shape as GET /v1/exceptions with an account filter). |
GET | /v1/accounts/coverage | Account / license coverage percentages, top missing-context sources, cardinality warnings. |
GET | /v1/licenses | Paginated list of observed licenses. |
GET | /v1/licenses/{licenseId} | One license’s detail (observed accounts, products, versions, active actors, recent activity). |
The accounts list and detail data is maintained by a background observation job that recomputes per-tenant rollups every ~60 seconds. Counts on the responses are 30-day rolling windows ending at the time the job last refreshed the row.
Several existing endpoints also accept optional account_id and license_id query parameters when account context is enabled — Event Explorer (/v1/reports/*), Sessions (/v1/sessions/*), Exceptions (/v1/exceptions), Environment (/v1/environment/*), Segments, Funnels, and Retention. Pages and detailed query shapes are documented at the OpenAPI spec; the wire schema is the source of truth.
Product configuration on responses
Product objects returned by GET /v1/products and GET /v1/products/{id} include a strict_versions_enabled: boolean field reflecting the current state of the strict-mode toggle. Set via PATCH /v1/products/{id} with optional strict_versions_enabled?: boolean (PATCH semantics — only applied when supplied). Requires manage_products permission.
SDKs
For most integrations, we recommend using an SDK instead of the API directly. SDKs handle batching, offline persistence, session management, breadcrumbs, and retry logic automatically.