@stormwateriq/api-client
v1.0.3
Published
Official TypeScript client for the StormwaterIQ external API: generated typed SDK over the gateway's OpenAPI contract, plus the reference webhook-signature verifier and automatic retries.
Readme
@stormwateriq/api-client
Official TypeScript client for the StormwaterIQ external API: a fully typed SDK generated from the gateway's OpenAPI contract, plus the reference webhook-signature verifier and automatic retries.
Release candidate. This is the
1.0.0-rcline, published on the npmrcdist-tag. Install it explicitly with@rcuntil1.0.0ships.
npm install @stormwateriq/api-client@rcUsage
createClient() returns a configured client (base URL + bearer auth + retries).
Pass it to any generated operation:
import { createClient, getSite, listSites } from "@stormwateriq/api-client";
const client = createClient({
token: accessToken, // OAuth2 client_credentials access token
// baseUrl defaults to https://api.stormwateriq.com
});
const { data: site } = await getSite({ client, path: { site_id: "site_123" } });
const { data: page } = await listSites({ client, query: { limit: 50 } });Authentication (createTokenSource)
createClient({ token }) accepts a ready access token, an async getter, or a
token source that mints + caches client-credentials tokens and refreshes them
ahead of expiry (single-flighting concurrent requests):
import { createClient, createTokenSource } from "@stormwateriq/api-client";
const tokens = createTokenSource({
clientId: process.env.SWIQ_CLIENT_ID!,
clientSecret: process.env.SWIQ_CLIENT_SECRET!,
scopes: ["site:read:owned", "site:write:owned"],
// tokenUrl defaults to DEFAULT_TOKEN_URL (https://api.stormwateriq.com/oauth/token)
});
const client = createClient({ token: tokens });Scope catalog: bmp:read:dumpster_enclosure, site:read, site:read:owned,
site:write:owned, inspection:read, inspection:read:owned, compliance:read,
certificate:generate, sponsor:read, sponsor:write:owned,
outcome:read:attributed, pollutant_loading:read, tmdl:read, ejscreen:read.
Generated operations follow the gateway's OpenAPI operationIds — e.g.
listSites, getSite, createSite, updateSite, listInspections,
getInspection, listInspectionPhotos, createWebhook, getSponsorOutcomes.
Every operation returns { data, error, request, response }; request/response
types are exported from the package.
Webhooks
import { webhooks } from "@stormwateriq/api-client";
// Verify an inbound webhook (constant-time, replay-protected):
const ok = webhooks.verify(
rawBody,
req.headers["x-stormwateriq-signature"],
signingSecret,
);webhooks.verify(payload, signature, secret) is byte-identical to the server and
the Python SDK, pinned to fixtures/webhook-signature-vectors.json.
After verifying, parse the body into a typed, discriminated event:
import { verifyWebhook, parseWebhookEvent } from "@stormwateriq/api-client";
if (!verifyWebhook(rawBody, signatureHeader, signingSecret)) return res.status(400).end();
const event = parseWebhookEvent(rawBody); // { id, type, created_at, data }
switch (event.type) {
case "site.registered":
// event.data is fully typed here
link(event.data.site_id, event.data.client_reference);
break;
case "inspection.completed":
refreshCard(event.data.site_id, event.data.deficiency_count);
break;
}The delivered body is the signed envelope { id, type, created_at, data } — id
is the stable per-event dedup key; type mirrors the X-StormwaterIQ-Event
header. Payload shapes (WebhookPayloadMap) mirror the server contract.
Behavior
- Retries — calls retry
429(honoringRetry-After) and5xxwith exponential backoff (maxRetries, default 3) viacreateRetryingFetch; other responses fail fast. - Errors — non-2xx responses throw an
ApiError(status,code,message) carrying the gateway's{ error, error_description }envelope.
Regenerating the client
The typed surface in src/generated/ is generated from the gateway's OpenAPI
document — never hand-edit it. After the API contract changes:
# 1. Re-emit the committed spec from the gateway
yarn workspace @stormwateriq/api-gateway emit:openapi
# 2. Regenerate this client
yarn workspace @stormwateriq/api-client openapi-tsPublished independently of the API (semver); breaking API changes drive an SDK major bump.
