REST API v1

Run CISO-level compliance assessments and passive website scans from your own tools, CI pipelines, or products. All endpoints return JSON.

Authentication

Create a key in Dashboard → API keys (free account required, max 10 active keys), then send it as a Bearer token. Keys are shown once at creation and stored hashed. Each key has a per-minute rate limit; exceeding it returns 429.

Authorization: Bearer sk_sc_...

POST /api/v1/assess

Scope: assess. Without answers, returns the question bank for the requested domains. With answers, returns scores, grades, category breakdowns, expert recommendations and applicable laws.

curl -X POST https://structureclerk.ca/api/v1/assess \
  -H "Authorization: Bearer sk_sc_..." \
  -H "Content-Type: application/json" \
  -d '{
    "domains": ["data_protection", "cybersecurity"],
    "jurisdictions": ["CA_QC", "EU"],
    "answers": {
      "data_protection": { "dp_1": true, "dp_2": "partial" }
    }
  }'
{
  "overallScore": 62,
  "grade": "B-",
  "domains": {
    "data_protection": {
      "score": 62,
      "answeredQuestions": 2,
      "categoryScores": { "...": { "percentage": 50 } },
      "topRecommendations": [
        {
          "questionId": "dp_2",
          "title": "…",
          "advice": "…",
          "quickWin": "…",
          "effort": "medium",
          "impact": "high",
          "timeline": "30 jours"
        }
      ]
    }
  },
  "applicableLaws": [
    { "jurisdiction": "Québec", "law": "Loi 25", "domain": "data_protection" }
  ]
}

POST /api/v1/scan

Scope: scan. Passive website compliance scan: HTTPS, privacy policy detection, cookie banner, HTTP security headers, contact information. Public pages only — no intrusion, no active testing.

curl -X POST https://structureclerk.ca/api/v1/scan \
  -H "Authorization: Bearer sk_sc_..." \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.example.com" }'
{
  "url": "https://www.example.com",
  "overallScore": 75,
  "checks": {
    "https": { "passed": true, "details": "…" },
    "privacyPolicy": { "found": true, "url": "…", "details": "…" },
    "cookieBanner": { "detected": false, "details": "…" },
    "securityHeaders": { "score": 57, "headers": { "…": {} }, "details": "…" },
    "contactInfo": { "found": true, "details": "…" }
  },
  "recommendations": ["…"]
}

Key management

Session-authenticated (browser) endpoints backing the dashboard UI:

  • GET /api/v1/keys — list your keys (prefixes only)
  • POST /api/v1/keys — create a key ({ "name": "...", "scopes": ["assess","scan","authority"] })
  • DELETE /api/v1/keys/:id — deactivate a key

Free endpoints

Public, no key, no credit. They were shipped and never documented here, which is the same as not shipping them.

  • GET /api/v1/pricing — the live rate card: credit cost per endpoint, top-up packs, the daily free allowance. Machine-readable, and the source the docs read from.
  • GET /api/v1/authority/verify/:evidence_id — verify a decision's Ed25519 signature. Free and account-free, permanently.
  • GET /api/v1/authority/verify/anchors — published anchors of the chained decision log, for third-party checking.
  • GET /api/v1/authority/stats — aggregate decision counts. No decision content, no identifiers.

Credits and the free allowance

The paid endpoints are metered in prepaid credits, with a daily free allowance consumed first. The refusals below are the ones you will actually receive; the full order of resolution, prices and response headers are on the Billing page.

Errors

StatusCodeMeaning
401unauthorized / invalid_api_keyMissing, invalid or expired key
403forbiddenKey lacks the required scope
429rate_limitedPer-key per-minute limit exceeded
403insufficient_scopeValid key, wrong scope. The body names the scope required and the scopes granted.
429free_tier_exhaustedAnonymous caller past the daily allowance. Use a key, or top up.
402insufficient_creditsBalance too low. The body carries the balance, the amount required and a recharge URL.
503metering_unavailableUsage counter unreachable. Anonymous calls refused; key holders served and billed directly.
413Request too largeBody over the endpoint limit, measured rather than declared.
400invalid_inputMalformed body — see the message field