Developers

Read-only score/fixes/citations, one write action (trigger an audit), and outbound webhook subscriptions — all under /api/v1/public/*, authenticated by API key rather than your portal login.

Quickstart

4 steps
  1. 1. Create a key

    In the portal, go to Settings → API Keys and mint one with the read scope, the trigger scope, or both. The raw key is shown once — store it like a password.

  2. 2. Make your first call

    Fetch the latest GIST Score for a site:

    curl https://api.gistai.app/api/v1/public/sites/SITE_ID/score \
      -H "Authorization: Bearer YOUR_API_KEY"
  3. 3. Register a webhook

    Pick one or more event types: audit_completed, fix_verified, score_changed. Subscriptions are account-wide (not per-site).

    curl -X POST https://api.gistai.app/api/v1/public/webhooks \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://example.com/gist-hook", "eventTypes": ["audit_completed"]}'
    
    # => { "data": { "secret": "…shown once, store it…", "webhook": { "id": "wh_…", … } } }
  4. 4. Verify each delivery

    Every delivery carries X-GIST-Signature: t=<unix_seconds>,v1=<hmac_sha256> (Stripe-style — the timestamp is signed alongside the raw body, so reject anything older than a few minutes as a possible replay).

    const crypto = require("crypto");
    
    function verifyGistSignature(secret, rawBody, header) {
      const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
      if (!match) return false;
      const [, timestamp, signature] = match;
    
      const expected = crypto
        .createHmac("sha256", secret)
        .update(`${timestamp}.${rawBody}`)
        .digest("hex");
    
      const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
      const expectedBuf = Buffer.from(expected, "utf8");
      const actualBuf = Buffer.from(signature, "utf8");
    
      return (
        ageSeconds <= 300 &&
        expectedBuf.length === actualBuf.length &&
        crypto.timingSafeEqual(expectedBuf, actualBuf)
      );
    }

    Sign over the exact raw request bytes you received — re-serializing a parsed JSON body can produce different bytes and fail verification even for a genuine delivery.

Full API reference

Raw OpenAPI 3.1 JSON ↗