Developers
Build on ChemSwatch
Everything in the web app is available through a versioned REST API, signed webhooks and an MCP server for AI agents.
REST API
Base URL http://localhost:3000/api/v1. JSON in and out, cursor pagination (?limit=&cursor= → { data, nextCursor }) and RFC 9457 problem details for errors.
Authentication
Create an API key under Settings → API keys and send it as Authorization: Bearer cs_live_…. Keys are scoped — grant only what an integration needs:
- sds:read
- sds:write
- inventory:read
- inventory:write
- risk:read
- risk:write
- org:read
- webhooks:manage
Keys are shown once, stored only as a SHA-256 hash, and can be revoked at any time. Requests are rate limited per key.
curl "http://localhost:3000/api/v1/products?q=hydrochloric&scope=all" \ -H "Authorization: Bearer $CHEMSWATCH_API_KEY"
curl -X POST "http://localhost:3000/api/v1/inventory" \
-H "Authorization: Bearer $CHEMSWATCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"locationId": "loc_…",
"productId": "prd_…",
"containerSize": 20,
"containerUnit": "L",
"containerCount": 3
}'curl -X POST "http://localhost:3000/api/v1/sds/upload" \ -H "Authorization: Bearer $CHEMSWATCH_API_KEY" \ -F "[email protected]" # → 202 { "id": "sds_…", "status": "processing", … } # Poll GET /api/v1/sds/{id} until status is "draft", then # POST /api/v1/sds/{id}/publish
MCP server for AI agents
Connect Claude (or any Model Context Protocol client) to your ChemSwatch workspace. The agent acts with the API key's scopes, so a read-only key gives read-only tools.
Tools
- search_chemicals
- get_chemical
- get_sds_section
- get_emergency_summary
- check_storage_compatibility
- list_inventory · find_chemical_locations
- update_inventory_quantity (inventory:write)
- suggest_risk_controls
- get_label · check_site_thresholds
- lookup_ghs_code · classify_hazard_codes
- list_sds_due_for_review
Prompts: emergency_response, pre_task_risk_assessment, storage_review.
{
"mcpServers": {
"chemswatch": {
"command": "npx",
"args": ["-y", "@chemswatch/mcp"],
"env": {
"CHEMSWATCH_API_KEY": "cs_live_…",
"CHEMSWATCH_API_URL": "http://localhost:3000/api"
}
}
}
}claude mcp add chemswatch \ --env CHEMSWATCH_API_KEY=cs_live_… \ --env CHEMSWATCH_API_URL=http://localhost:3000/api \ -- npx -y @chemswatch/mcp
# Any MCP client that supports Streamable HTTP URL: http://localhost:3000/mcp Header: Authorization: Bearer cs_live_… # Claude Code claude mcp add --transport http chemswatch http://localhost:3000/mcp \ --header "Authorization: Bearer cs_live_…"
Webhooks
Subscribe to product.*, sds.*, inventory.* and risk_assessment.* events. Each delivery is a JSON POST signed with your endpoint secret:
ChemSwatch-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "t.body")>
Failed deliveries are retried with exponential backoff, and every attempt is visible in the delivery log.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyChemSwatchSignature(secret: string, header: string, rawBody: string, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const given = Buffer.from(parts.v1 ?? "", "hex");
const want = Buffer.from(expected, "hex");
return given.length === want.length && timingSafeEqual(given, want);
}