Counted for developers
API integration
Collect events, ask questions, and manage your workspace from code or the API Explorer.
Start without an account
Send POST /v1/projects/provision with an optional project name. The response contains a working ingest key, a project ID, and an expiring claim token. Keep the claim token private.
When you are ready to keep the project, claim it in the app. Sign in or create an account, choose a workspace, and paste the project ID and claim token. You need permission to create projects in that workspace and an available project slot. Existing events and the ingest key are retained.
Automation can use POST /v1/projects/{projectId}/claim with a service key authorized for the destination workspace. The claim token proves ownership of the unclaimed project; it does not replace workspace authorization.
Make your first request
Open API Explorer in the Counted app. Choose an operation and send a request using your signed-in session. Workspace fields start with your current workspace; you can edit every input.
For access across projects, open Settings → Workspace → Workspace keys → New service key. Select the permissions and expiry, then copy the secret before closing. Owners and admins can inspect, rotate, and revoke keys from that table. For access to one project, use its Keys page. Send your service key as a bearer token to https://api.counted.dev.
curl https://api.counted.dev/v1/me \
-H "Authorization: Bearer $COUNTED_SERVICE_KEY"A service key’s access is limited by its permissions, project or workspace scope, and the issuing member’s role.
Collect events
Use a project’s ingest key with a Counted SDK. The SDK handles batching, retries, and ephemeral visits. An ingest key can write events to one project; it cannot read analytics or manage your workspace.
Direct ingestion uses POST /v1/events with a JSON body containing an events array and an Authorization: Bearer header. A 202 response acknowledges a durable write and reports accepted, deduplicated, and rejected events. Inspect any per-event outcomes; a successful batch can still contain rejected events.
COUNTED_VISIT_ID=$(uuidgen)
curl https://api.counted.dev/v1/events \
-H "Authorization: Bearer $COUNTED_INGEST_KEY" \
-H "Content-Type: application/json" \
--data "{\"events\":[{\"name\":\"page_view\",\"visitId\":\"$COUNTED_VISIT_ID\",\"properties\":{\"url\":\"/pricing\"}}]}"This one-off request uses a fresh visit id and the server’s receipt time. In an application, let the SDK keep visits in memory and supply an occurredAt timestamp and unique idempotencyKey for every event. Keep both unchanged when retrying an event so it can be deduplicated. Never store a visit id in a cookie or local storage.
In API Explorer, choose Events → Ingest events. Enter the project’s ingest key, edit the generated event fields or JSON, and send. The sample contains a fresh visit, timestamp, and event key. A signed-in session cannot ingest; the project is determined entirely by the key.
Custom properties are flat strings, numbers, booleans, or null. For example, {"url":"/pricing","campaign":"launch"}can become filters or breakdown dimensions in an Insight. Omit personal data. Geography comes from the request at the edge; the IP address is discarded and client-supplied country values are ignored.
The reference includes ingestion schemas from the same wire contract as the dedicated group-commit handler. Authentication remains under the provider’s /api/auth/ routes, which the Explorer’s Custom request tab can call through the app’s API proxy.
Read the receipt before retrying
{
"accepted": 1,
"deduplicated": 0,
"rejected": 1,
"outcomes": [
{
"index": 1,
"accepted": false,
"reason": "MalformedEvent"
}
]
}Here the first event is stored and the second is rejected. outcomes uses zero-based indexes from your submitted array and lists only rejected events. Fix those events before submitting them again. A deduplicated event is already stored; nothing more is required. With no rejections, outcomesis omitted.
| Status | Next step |
|---|---|
| 202 | Inspect accepted, deduplicated, rejected, and any per-event outcomes. |
| 400 / 413 | Correct the JSON or split the batch. Defaults allow 250 events and 1 MiB. |
| 401 / 403 / 404 | Check the key, its project, and events:write permission. Do not retry unchanged. |
| 402 | Resolve the workspace quota or wait for its next usage period. |
| 429 | Wait at least Retry-After seconds, then retry with the same event keys and timestamps. |
| 503 | Back off and retry with the same event keys and timestamps. |
Whole-batch failures include code, detail, and retryable. Limits may differ on a self-hosted deployment. The full reference describes every field and response.
Build with the contract
The API reference includes request fields, response schemas, errors, and examples for every oRPC operation. Download the OpenAPI 3.1 document to generate a client or import it into your API tools.
In the Explorer, switch between generated forms and JSON for complex requests. Choose a service or ingest key to test its exact permissions, or use a share token for a shared dashboard. Entered keys and request contents stay in memory while you use the page.