✦ API Documentation

Your engagement data, in your own tools

A read-only REST API over your surveys, runs, aggregated results, people and dashboard metrics. Bearer-token authentication, cursor pagination, and a versioning promise written down rather than implied. Included with the Enterprise plan — a custom annual contract, not a self-serve upgrade. Talk to us.

Authentication

One header, one key

Create a key in Settings → API. The full key is shown once, at creation. We store a SHA-256 hash of it, so if it's lost the only path forward is to revoke it and mint a new one.

# Every request carries the key as a bearer token.
curl https://app.bloomder.io/api/v1/surveys \
  -H "Authorization: Bearer blm_live_YOUR_KEY_HERE"

What a key can and cannot do

  • Scoped to the organization that created it. There is no cross-organization key.
  • Read-only. The API has no write endpoints at all, so a leaked key cannot alter, delete or send anything.
  • Revocable at any time by the account owner, taking effect on the next request.
  • Optionally set to expire after a number of days. Left blank, a key does not expire — a silent expiry would break a customer's nightly job months after they set it up.

Treat a key like a password

A key grants read access to your whole organization's engagement data. Keep it in a secret manager, never in client-side code or a public repository, and revoke it the moment you suspect exposure. Every creation and revocation is recorded in your audit log.

Anonymity

What this API will not return

The anonymity guarantee is a property of the platform, not of the dashboard, so it applies here identically. This is the shortest honest summary of what you cannot get, even with a valid key.

  • No individual responses. There is no endpoint that returns a single person's answers, because the platform deletes the link between a response and a person at the moment the response is saved.
  • No small-group results. Aggregates for a run with fewer than five distinct respondents come back as suppressed: true, carrying only the participation counts — which are known from the send list, not from any answer.
  • No way to lower the threshold. The floor of five is fixed in the deployed platform and cannot be changed by an administrator, by a query parameter, or by us.

Segmentation by department still works, because a department is a group attribute recorded when the invitation is sent, not an identity. Read the longer explanation on the security page.

Pagination

Cursors, not page numbers

List endpoints return a page of data and a next_cursor. Pass that cursor back to get the next page; null means you've reached the end. Cursors are stable under inserts, which offset-based paging is not — a survey created mid-scan cannot make you skip or repeat a row.

# First page: 50 surveys.
curl "https://app.bloomder.io/api/v1/surveys?limit=50" \
  -H "Authorization: Bearer $BLOOMDER_KEY"

# Response
{
  "data": [ { "id": "clx…", "name": "Q3 Pulse", "status": "ACTIVE", … } ],
  "next_cursor": "clx8f2k…"
}

# Next page.
curl "https://app.bloomder.io/api/v1/surveys?limit=50&cursor=clx8f2k…" \
  -H "Authorization: Bearer $BLOOMDER_KEY"

limit defaults to 25 and is capped at 100. A larger value is clamped, not rejected.

Errors

Branch on the code, not the message

Every error returns the same envelope. code is a stable string that is part of the contract; error is human-readable prose that may be reworded at any time. Write your integration against the code.

{
  "error": "The public API is not included in this plan. It is part of the Enterprise plan — contact us to enable it.",
  "code": "plan_required"
}
StatusCodeWhat it means
400invalid_requestA parameter was malformed.
401invalid_api_keyThe key is missing, unknown, revoked or expired. One code for all four, deliberately: the response can't be used to probe which keys once existed.
402plan_requiredThe organization's plan does not include API access.
404not_foundNo such resource in this organization. An id belonging to another customer returns 404, never 403.
429rate_limitedRate limit exceeded. Honour the Retry-After header.
500internal_errorSomething failed on our side. The body carries a request_id — quote it in a support request and we can find the exact log line.
Rate limits

120 requests per minute, per key

The budget is per key rather than per organization or per IP: your integration runs from wherever your infrastructure happens to be, and two keys in one account should not compete for the same allowance.

  • X-RateLimit-Limit and X-RateLimit-Window come back on every response, not only on a 429, so a client can pace itself before it gets refused.
  • On a 429, Retry-After gives the seconds to wait. Respect it rather than retrying immediately.
  • Need more for a bulk backfill? Write to support@bloomder.io and tell us the shape of the job.
Versioning

What we may change, and what we won't

An API is a promise about the future, so here is the exact promise. Every response also carries X-Bloomder-Api-Version with the date of the deployed contract.

Additive changes ship into /v1 without notice

  • New endpoints.
  • New fields in an existing response. Parse tolerantly: an unknown field is not an error.
  • New values in an enum-like field, where the field already documents that it may grow.

Breaking changes never ship into /v1

Removing a field, renaming one, changing its type, or changing what an existing value means — all of those create /api/v2 instead. When that happens, /v1 keeps serving for at least six months and starts returning a Sunset header with the date it stops. You will also hear it from us by email before the header appears.

The OpenAPI document is the specification

This page explains the API; openapi.json defines it, and needs no key to read. Point Postman, Insomnia, or your generator of choice at it.

Reference

Every endpoint, in one table

All paths are relative to https://app.bloomder.io/api/v1. There are no write endpoints.

EndpointReturnsParameters
GET/surveys Your surveys, newest first, with question and run counts. limit, cursor
GET/surveys/{id} One survey with its questions, its audience (departments, categories, divisions) and its schedule.
GET/surveys/{id}/runs The survey's runs with invitations sent, responses received and response rate. limit, cursor
GET/surveys/{id}/results Aggregated results for one run: per-question distributions and averages, NPS, wellness average, department breakdown. Suppressed below five respondents. run_id (omit for the latest run)
GET/employees People on your list with their department, category and division. No response data. limit, cursor, include_inactive
GET/departments Your departments.
GET/categories Your employee categories, used for survey targeting.
GET/divisions Your divisions.
GET/action-items Action items with priority, status, owner and due date. limit, cursor
GET/metrics/dashboard The same aggregates the dashboard renders for a time window. range (7, 28, 90, 180, 365, all, Q1Q4), department_id
GET/openapi.json The OpenAPI 3.1 document. No authentication required.

A complete example

Pull the latest results for every active survey — the shape most BI refreshes take.

#!/usr/bin/env bash
# Requires: curl, jq. Set BLOOMDER_KEY in your environment.
set -euo pipefail
BASE="https://app.bloomder.io/api/v1"
AUTH=(-H "Authorization: Bearer $BLOOMDER_KEY")

curl -s "$BASE/surveys?limit=100" "${AUTH[@]}" \
  | jq -r '.data[] | select(.status == "ACTIVE") | .id' \
  | while read -r id; do
      curl -s "$BASE/surveys/$id/results" "${AUTH[@]}" \
        | jq '{survey: .survey_name, rate: .run.response_rate, suppressed, nps: .nps_score}'
    done

Ready to wire it up?

API access is part of the Enterprise plan, a custom annual contract. Talk to us about what you're building and we'll set it up; once it's on, you create keys from your account settings.

Questions about a specific integration? Write to support@bloomder.io.