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.

Search the library
curl "http://localhost:3000/api/v1/products?q=hydrochloric&scope=all" \
  -H "Authorization: Bearer $CHEMSWATCH_API_KEY"
Record stock in a cabinet
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
  }'
Upload an SDS PDF
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.

Claude Desktop — claude_desktop_config.json (stdio)
{
  "mcpServers": {
    "chemswatch": {
      "command": "npx",
      "args": ["-y", "@chemswatch/mcp"],
      "env": {
        "CHEMSWATCH_API_KEY": "cs_live_…",
        "CHEMSWATCH_API_URL": "http://localhost:3000/api"
      }
    }
  }
}
Claude Code (stdio)
claude mcp add chemswatch \
  --env CHEMSWATCH_API_KEY=cs_live_… \
  --env CHEMSWATCH_API_URL=http://localhost:3000/api \
  -- npx -y @chemswatch/mcp
Remote MCP over Streamable HTTP
# 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.

Verify a webhook (Node.js)
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);
}