Skip to main content

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.

RouteAuthAcceptsUsed by
POST /v1/pingNone — public, unauthenticatedtelemetry_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/internalSigV4 (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.

FieldTypeRequiredNotes
telemetry_typestringRequired on v1 emitters; empty accepted during the migration windowOne of sdk, plugin, platform. Rejected with HTTP 400 if synthetic (use the internal route) or if outside the closed set.
sdkstringClass-dependentRequired for sdk and plugin (must be in the corresponding allowlist); must be empty for platform.
sdk_versionstringYesSemver.
componentstringRequired when telemetry_type=platform; must be empty otherwiseOne of agent, orchestrator.
deployment_modestringYesTopology classification (self_hosted / community_saas / unknown).
endpoint_typestringOptionalSDK-derived classification of the configured AxonFlow URL. The raw URL is never sent.
streamstringOptionalOne 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_tierstringOptionalOne of Community, Evaluation, Professional, Enterprise, EnterprisePlus, unknown. Server-side normalization: case variants and known aliases (PlusEnterprisePlus, communityCommunity) collapse to canonical form; unrecognized non-empty values bucket as unknown (no HTTP 400). Empty passes through.
environment_classstringOptionalOne of lambda, ecs_fargate, ecs_ec2, kubernetes, container, bare_metal, unknown. Same fail-soft normalization as license_tier — unrecognized values bucket as unknown.
os, archstringYesRuntime environment.
runtime_versionstringYesLanguage / runtime version.
platform_versionstringOptionalDetected from AxonFlow's /health endpoint (best-effort).
featuresstring arrayOptionalPlugin hook configuration summary.
instance_idstringYesRandom per-machine identifier for de-duplication.
occurred_atstringRejected if non-nullRFC3339. 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.

  1. telemetry_type is one of {sdk, plugin, platform, ""}. (synthetic is reserved for the internal route.)
  2. Class-specific guards. When telemetry_type=sdk, sdk must be a recognized language SDK (go, python, typescript, java, rust). When telemetry_type=plugin, sdk must be a recognized plugin identifier (openclaw-plugin, claude-code-plugin, cursor-plugin, codex-plugin). When telemetry_type=platform, sdk must be empty AND component must be one of {agent, orchestrator}.
  3. Migration-window inference. If telemetry_type is empty, the class is inferred from sdk-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 empty telemetry_type becomes a hard reject.
  4. stream ∈ {"", heartbeat, sandbox}. Empty defaults to heartbeat. Any operationally-derived stream value (e.g. community_saas_operational) is rejected on this route.
  5. occurred_at must 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

StatusCause
400Validation failure — see the validation matrix above. Body: {"error": "..."}.
429Rate-limit exceeded — the public route is throttled at the API Gateway layer (50 burst / 100 sustained per source IP, unchanged by v1).
5xxBackend 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

AspectPublic /v1/pingInternal /v1/ping/internal
AuthNoneAWS SigV4, AWS_IAM API Gateway authorizer
telemetry_type acceptedsdk / plugin / platform (or empty during migration)synthetic only — any other value is rejected
occurred_atRejected if non-nullHonored (RFC3339), so backdated aggregates land at the original event time
streamWire-allowlist heartbeat / sandbox (default heartbeat); community_saas_operational rejectedServer may persist community_saas_operational for synthetic rows; stream still validated against the closed set
sdk fieldRequired for sdk/plugin classesn/a — synthetic rows do not represent a binary that has an sdk
instance_idRandom per-machine identifierBridge-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

AspectLimit
Burst rate per source IP50 requests / second
Sustained rate per source IP100 requests / second over the API Gateway window
QuotaNone — 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=off covers and the synthetic-vs-heartbeat separation
  • Deployment Mode Matrix — how deployment_mode aligns to the deployment topology