Sign in →
1 min read

SDKs & Client Libraries

Where and how to download every Aforo metering client — base SDKs (Node, Python, Go, Java), protocol SDKs, MCP tooling, and gateway plugins.

Updated 2026-11-11Suggest edits
Get the SDKs & plugins
Every metering SDK, protocol client, and gateway plugin lives in one public repo: github.com/aforoai/SDKs
View on GitHub Download ZIPgit clone https://github.com/aforoai/SDKs.git

When to Use SDKs#

Reach for an SDK when your service already knows who the customer is and what to count, and you want Aforo to own buffering, batching, and retry. Every SDK is emit-only: track(...) puts the event on a bounded queue and returns immediately, and one background sender per client ships batches to the ingestor — so metering never sits in your request path.

No gateway present
Internal microservices, background jobs, or CLI tools that bypass the API gateway
Drop-in middleware
Meter one event per HTTP request with a framework adapter (Express, FastAPI, Chi, a Spring filter) — no per-route code
Custom metering logic
Count computed metrics (tokens processed, MB stored, minutes elapsed) that the gateway never sees, with a manual track() call at the point you know the quantity
Hybrid coverage
Gateway plugins meter edge traffic; SDKs meter the internal services and batch jobs behind them
INFO
All four SDKs live in one public distribution repo: github.com/aforoai/SDKs (under aforo-metering-sdks/). The packages are on npm, PyPI, Maven Central, and the Go module proxy — install with your package manager below. Workspace scope comes from the API key; there is no tenantId argument, and these SDKs only emit usage — they do not read entitlements or block requests.

Install & Quickstart#

Pick your language — the tab below switches the install command and a copy-paste quickstart.

@aforoai/metering 2.0.0 requires Node 18+ (it uses the built-in fetch). Events POST to https://api.aforo.ai/v1/ingest/batch with the key in X-API-Key. baseUrl must be https:// (plain http only for localhost, or with allowInsecureHttp: true).

terminal
npm i @aforoai/metering

Manual Tracking

usage.js
import { AforoClient } from '@aforoai/metering';

const aforo = new AforoClient({
  apiKey: process.env.AFORO_API_KEY,
  productType: 'API', // default; sent as top-level productType on every event
});

// Count one billable action — queued, sent in the background. Never throws.
await aforo.track({
  customerId: 'cust_123',
  metricName: 'api_calls',
  quantity: 1,
});

// The SDK installs no signal handlers. Flush from your own shutdown path:
process.once('SIGTERM', async () => {
  await aforo.shutdown(); // sends what is queued, for at most shutdownTimeoutMs (5 s)
  process.exit(0);
});

Express Middleware

app.js
import { AforoClient } from '@aforoai/metering';
import { expressMiddleware } from '@aforoai/metering/middleware/express';

const aforo = new AforoClient({ apiKey: process.env.AFORO_API_KEY, productType: 'API' });

// Meters every request after the response is sent — zero added latency
app.use(expressMiddleware({
  client: aforo,                                          // your client: you flush and shut it down
  customerResolver: (req) => req.user?.aforoCustomerId,   // required: identity your app authenticated
  metricName: 'api_calls',                                // or (req, res) => string; must exist in your catalog
}));

The middleware hooks res.on('finish'), so it runs after the response. The default metric is api_calls, and it must exist in your catalog — the ingestor rejects events for an unknown metric. Creating the middleware without customerResolver throws, unless you opt in to trustCustomerHeader: true (reads X-Customer-Id — only safe when a proxy you control sets that header and strips any client copy). A request whose resolver returns nothing is served normally; its event is dropped as invalid and counted. OPTIONS requests are not metered. Fastify and Koa adapters (fastifyPlugin, koaMiddleware) live under the same @aforoai/metering/middleware/* path and take the same options.

Who Is Billed#

Every integration that meters an incoming request — HTTP middleware, the gRPC / GraphQL / WebSocket / MQTT clients, the MCP SDKs and the MCP proxy — needs to know which customer to bill, and from 2.0.0 you tell it. Pass a customer resolver that reads identity your service already verified (a session, a JWT subject, an API-key lookup, an mTLS identity).

  • Reading a value the caller of your API controls — the X-Customer-Id header, gRPC metadata, MCP _meta.customer_id, the MQTT username — needs an explicit opt-in: trustCustomerHeader (Node, Java), trust_customer_header (Python), TrustCustomerHeader (Go). Turn it on only when a proxy you control sets the value and strips any client copy.
  • With neither a resolver nor the opt-in, construction fails (Node throws, Python raises ValueError, Go returns an error, Spring fails to start). There is no fallback to unknown, anonymous or the agent id.
  • When the resolver returns nothing for one request, the request runs normally and its event is dropped as invalid and counted.
  • A manual track() call is unchanged: you pass customerId yourself.

Delivery & Dropped Events#

All 2.0.0 SDKs send the same way. track() only queues (bounded, 10,000 events by default; on overflow the oldest is evicted). One background sender per client sends one request at a time: every 5 s, or as soon as 500 events are waiting (the batch size, capped at 1,000). Retries on network errors, timeouts, 408, 429 and 5xx use full-jitter backoff and honour Retry-After (seconds or an HTTP date, capped at 60 s).

An outage does not lose usage. After the last retry — or on 401 / 403, which usually means a wrong or revoked key and is logged as an error — the batch goes back to the front of the queue with the same idempotency keys and a breaker pauses sending (5 s, doubling to 5 min). New events keep queueing; the first successful request resumes sending, and the ingestor deduplicates the re-sent keys. Usage is lost only if the queue overflows first, so raise the queue size if you expect long outages. A 413 splits the batch in half.

Shutdown is bounded: shutdown() / close() keeps sending for at most the shutdown timeout (5 s by default) and reports what is still queued. The libraries install no signal handlers — call shutdown from your own SIGTERM path. Python also flushes at atexit, Node on beforeExit, Java in Spring's stop phase.

Drop reasonWhen
overflowThe queue was full (usually during a long outage); the oldest event was evicted.
rejectedThe ingestor refused the batch with a non-retryable 4xx, refused individual events inside an accepted batch, or answered 413 to a single event. Unknown metric names land here.
invalidThe event failed a client-side check before it was queued (blank customer or metric, quantity <= 0, a field over the ingestor limit), or a customer resolver returned nothing.
shutdownStill queued when the shutdown timeout ran out.
closedtrack() was called after shutdown. It does not throw (Go returns ErrClientClosed).
retry_exhaustedKept for API compatibility only — an outage requeues instead.

Every dropped event is counted, logged, and passed to the opt-in onDrop(events, reason) hook (on_drop in Python, OnDrop in Go). Events keep their idempotency keys, so re-tracking them after recovery does not bill twice.

More SDKs & Plugins in the Repo#

The same public repo carries metering clients beyond the four base SDKs above. Clone it — or use Download ZIP — and follow each package's README; every package ships its own install + usage steps.

Protocol SDKs

Meter at the protocol layer instead of per-HTTP-request. Each protocol ships for Node, Python, Go, and Java — e.g. aforo-metering-sdks/node-graphql, python-grpc. The exact call API is in each package's README.

If a rate plan uses Outcome-Based pricing, each protocol client sets executionStatus differently:

  • gRPC derives it from the status code with the same table as the Kong gateway plugin (OK → SUCCESS, DEADLINE_EXCEEDED → TIMEOUT, UNAUTHENTICATED / PERMISSION_DENIED / RESOURCE_EXHAUSTED → BLOCKED, … — see ).
  • GraphQL derives it from the response: errors with no data key → VALIDATION_FAILED; "data": null → ERROR; errors plus data → PARTIAL.
  • WebSocket and MQTT send it only when you pass it.

Every SDK checks a status you pass against the 11 the ingestor accepts. An unknown one is logged and left off the event (gRPC and GraphQL send the derived status instead) — sent as-is, the ingestor would reject the event and its usage would be lost.

ProtocolWhat it metersPackage suffix
GraphQLOperations (query/mutation/subscription) with complexity scoring{lang}-graphql
gRPCUnary and streaming RPC calls{lang}-grpc
WebSocketConnection lifetime, message count + bytes{lang}-ws
MQTTPublish / subscribe message volume{lang}-mqtt

AI & MCP

PackageWhat it doesRepo path
MCP metering SDKMeter MCP tool invocations from your servernode-mcp · python-mcp
MCP transport proxyMeter stdio / SSE / Streamable HTTP MCP servers with no code change (CLI sidecar). HTTP listeners need a bearer token from 2.0.0mcp-proxy
Agent SDKSession + step metering for AI agentsnode-agent · python-agent

Background on the agent + MCP surfaces: (including how each tool call's executionStatus is set) and .

Gateway Plugins — zero code

Meter at the edge with no application changes. Each lives under aforo-gateway-plugins/ and has a step-by-step guide:

An EMQX (MQTT broker) plugin also ships in the repo under aforo-emqx-plugin/.

SDK vs. Gateway: When to Use Which#

CriteriaGateway PluginSDK
Code changesZeroMinimal (middleware) or a manual track() call
Metering granularityRequest-levelCustom (tokens, bytes, compute time)
Where it runsAt the edge, after the responseIn-process, after the response (non-blocking buffer)
Customer identityVerified JWT claim / subscription idA customer resolver over identity your app verified, or a value you pass to track()
Best forAPI products fronted by a gatewayInternal services, CLI tools, background jobs
PRO TIP
Run both where it fits: a gateway plugin meters the API traffic that passes through your gateway with no code change, and an SDK meters what the gateway never sees — internal services, background jobs, and custom metrics like token counts where the quantity isn't one-per-request.
Aforo Documentation — Aforo Docs