Checkpoint Service API
The checkpoint service ingests AxonFlow telemetry pings under the v1 schema. Two routes — one public, one internal-only — produce a single typed table downstream. For the conceptual framing of the four emitter classes (sdk, plugin, platform, synthetic) and the privacy boundary, start with Telemetry.
| Route | Auth | Accepts | Used by |
|---|---|---|---|
POST /v1/ping | None — public, unauthenticated | telemetry_type ∈ {sdk, plugin, platform}, or empty during the v1 migration window. Rejects synthetic and any caller-supplied occurred_at. | All SDK, plugin, and platform clients in the wild. |
POST /v1/ping/internal | SigV4 (AWS IAM) | telemetry_type=synthetic only. Honors caller-supplied occurred_at (RFC3339). | AxonFlow-operated server-side bridges. |
Both routes terminate at the checkpoint Lambda handler and produce records in the same downstream table, distinguished by telemetry_type and stream.
POST /v1/ping
Ingests heartbeat pings from SDKs, plugins, and platform binaries. Public, unauthenticated by design — the entire payload is anonymous and does not include credentials, prompts, tool arguments, policy data, or PII. The server stamps Timestamp from API Gateway's request context; client-supplied timestamps are rejected (see below).
Request body
JSON. The shape is shared across the three accepted emitter classes; telemetry_type discriminates which fields are required and which are rejected. See Telemetry for worked examples per class.
| Field | Type | Required | Notes |
|---|---|---|---|
telemetry_type | string | Required on v1 emitters; empty accepted during the migration window | One of sdk, plugin, platform. Rejected with HTTP 400 if synthetic (use the internal route) or if outside the closed set. |
sdk | string | Class-dependent | Required for sdk and plugin (must be in the corresponding allowlist); must be empty for platform. |
sdk_version | string | Yes | Semver. |
component | string | Required when telemetry_type=platform; must be empty otherwise | One of agent, orchestrator. |
deployment_mode | string | Yes | Topology classification (self_hosted / community_saas / unknown). |
endpoint_type | string | Optional | SDK-derived classification of the configured AxonFlow URL. The raw URL is never sent. |
stream | string | Optional | One of "" (defaults to heartbeat), heartbeat, sandbox. community_saas_operational is rejected with HTTP 400 — it is reserved for the synthetic ingestion path and may not originate from a client. |
license_tier | string | Optional | One of Community, Evaluation, Professional, Enterprise, EnterprisePlus, unknown. Server-side normalization: case variants and known aliases (Plus → EnterprisePlus, community → Community) collapse to canonical form; unrecognized non-empty values bucket as unknown (no HTTP 400). Empty passes through. |
environment_class | string | Optional | One of lambda, ecs_fargate, ecs_ec2, kubernetes, container, bare_metal, unknown. Same fail-soft normalization as license_tier — unrecognized values bucket as unknown. |
os, arch | string | Yes | Runtime environment. |
runtime_version | string | Yes | Language / runtime version. |
platform_version | string | Optional | Detected from AxonFlow's /health endpoint (best-effort). |
features | string array | Optional | Plugin hook configuration summary. |
instance_id | string | Yes | Random per-machine identifier for de-duplication. |
occurred_at | string | Rejected if non-null | RFC3339. Reserved for the internal route. Sending it on the public route fails validation with HTTP 400 to prevent timestamp spoofing of analytics rows by unauthenticated callers. |
Validation matrix
The handler runs the following checks in order; the first failure returns HTTP 400 with a JSON {"error": "..."} body.
telemetry_typeis one of {sdk,plugin,platform,""}. (syntheticis reserved for the internal route.)- Class-specific guards. When
telemetry_type=sdk,sdkmust be a recognized language SDK (go,python,typescript,java,rust). Whentelemetry_type=plugin,sdkmust be a recognized plugin identifier (openclaw-plugin,claude-code-plugin,cursor-plugin,codex-plugin). Whentelemetry_type=platform,sdkmust be empty ANDcomponentmust be one of {agent,orchestrator}. - Migration-window inference. If
telemetry_typeis empty, the class is inferred fromsdk-field membership: in the plugin allowlist →plugin; in the language-SDK allowlist →sdk; otherwise the request is rejected. The migration window will close after the v8.1 SDK minors land; once closed, an emptytelemetry_typebecomes a hard reject. stream∈ {"",heartbeat,sandbox}. Empty defaults toheartbeat. Any operationally-derived stream value (e.g.community_saas_operational) is rejected on this route.occurred_atmust be unset. Any non-null value is rejected.
license_tier and environment_class are intentionally not in this list — both fail soft. Unrecognized non-empty values bucket as unknown server-side rather than producing HTTP 400, so a lagging or misconfigured emitter still lands its row and stays visible in analytics.
Response
200 OK with a PingResponse body:
{
"latest_version": "7.8.0",
"alerts": [
{
"level": "info",
"message": "..."
}
]
}
latest_version is the platform's current stable release; clients can use it to surface "you're running a stale version" advisories. alerts is an optional array of {level, message} objects (level ∈ {info, warning, critical}).
Errors
| Status | Cause |
|---|---|
| 400 | Validation failure — see the validation matrix above. Body: {"error": "..."}. |
| 429 | Rate-limit exceeded — the public route is throttled at the API Gateway layer (50 burst / 100 sustained per source IP, unchanged by v1). |
| 5xx | Backend error — the client should treat this as transient and retry on the next 7-day boundary; do not advance the stamp file. |
Example — SDK ping
curl -s -X POST https://checkpoint.getaxonflow.com/v1/ping \
-H 'Content-Type: application/json' \
-d '{
"telemetry_type": "sdk",
"sdk": "python",
"sdk_version": "7.1.0",
"platform_version": "7.8.0",
"os": "linux",
"arch": "arm64",
"runtime_version": "python 3.12.1",
"deployment_mode": "self_hosted",
"endpoint_type": "private_network",
"stream": "heartbeat",
"instance_id": "f3a81c12-4b2e-4d31-a8f3-12c45d6e7f89"
}'
For plugin, platform, and synthetic payload examples, see Telemetry — worked example payloads.
POST /v1/ping/internal
The SigV4-authenticated counterpart to /v1/ping. Accepts only the synthetic emitter class — server-side bridge events derived from operational data already inherent to running the hosted service. The route exists to bridge those derived analytics into the same typed table as the heartbeat path, so a single read query can answer questions across both.
This route is internal but not secret — security comes from the IAM grant model, not from obscurity. The route name and shape are documented here so a privacy-conscious reader can understand the full surface; the IAM grant + role configuration that enables a particular bridge to call it lives in private operator runbooks and is not published.
What's different from /v1/ping
| Aspect | Public /v1/ping | Internal /v1/ping/internal |
|---|---|---|
| Auth | None | AWS SigV4, AWS_IAM API Gateway authorizer |
telemetry_type accepted | sdk / plugin / platform (or empty during migration) | synthetic only — any other value is rejected |
occurred_at | Rejected if non-null | Honored (RFC3339), so backdated aggregates land at the original event time |
stream | Wire-allowlist heartbeat / sandbox (default heartbeat); community_saas_operational rejected | Server may persist community_saas_operational for synthetic rows; stream still validated against the closed set |
sdk field | Required for sdk/plugin classes | n/a — synthetic rows do not represent a binary that has an sdk |
instance_id | Random per-machine identifier | Bridge-stable identifier (e.g., axonflow-csaas-bridge) |
Privacy boundary
Synthetic events are not user telemetry — they are derived from operational data already processed inherent to running the hosted service (try.getaxonflow.com). They do not breach the SDK / plugin / platform heartbeat opt-out because they do not originate from a user-controlled binary. The Stream=community_saas_operational tag on synthetic rows means downstream readers can filter them out of the heartbeat analytics dataset by the same stream predicate that protects the heartbeat boundary on the write side. See Telemetry — Stream and the privacy boundary.
Throttling and quotas
| Aspect | Limit |
|---|---|
| Burst rate per source IP | 50 requests / second |
| Sustained rate per source IP | 100 requests / second over the API Gateway window |
| Quota | None — the route is heartbeat-cadence by design (one ping per environment per 7 days for SDK / plugin); no per-account daily cap applies |
These limits are enforced at the API Gateway layer and are unchanged by the v1 schema rollout.
Versioning
The /v1/ping and /v1/ping/internal routes are stable under the v1 contract. Field additions follow the additive-with-omitempty rule used elsewhere in AxonFlow's APIs — clients should tolerate unknown fields. Field removals or rename require a major route bump (/v2/ping); the migration window note above is internal to the v1 contract (it only affects when the validator tightens, not the route shape).
See also
- Telemetry — conceptual reference, opt-out semantics, four worked example payloads
- Privacy Policy — what
AXONFLOW_TELEMETRY=offcovers and the synthetic-vs-heartbeat separation - Deployment Mode Matrix — how
deployment_modealigns to the deployment topology
