Developers
Build on the RegSignal API.
REST under /v1, one API key header, signed webhooks, TypeScript and Python SDKs, an OpenAPI spec and a remote MCP server. Every header, path and number on this page is taken from the code that runs the API.
API overview
The REST API
JSON over HTTPS, one header to authenticate, cursors for paging, and plan ceilings you can read back from the API itself.
- Base URL
https://api.regsignal.dev- Versioning
- Every route lives under
/v1. The spec version today is 0.1.0. - Authentication
- Send your key in
X-API-Key;Authorization: Bearerwith the key also works. Keys start withrk_live_, are created by admins in Settings > API keys, are shown once, are stored only as a SHA-256 hash, and act with the analyst role. - Pagination
- The signal feed uses keyset cursors: set
limit(1 to 200, default 50), then passnext_cursorback ascursorwhilehas_moreis true. Useupdated_afterfor incremental sync. A few lists, such as a profile’s impact matches, uselimitandoffset. - Errors
- JSON with a
detailfield. Plan and size errors add a machine-readablecode. The full table is below.
# One page of EU and US signals (1 unit)
curl -s "https://api.regsignal.dev/v1/signals?jurisdictions=EU,US&limit=20" \
-H "X-API-Key: $REGSIGNAL_API_KEY"
# The next page: send next_cursor back as cursor while has_more is true
curl -s "https://api.regsignal.dev/v1/signals?jurisdictions=EU,US&limit=20&cursor=$NEXT_CURSOR" \
-H "X-API-Key: $REGSIGNAL_API_KEY"
# Ask a question, get an answer with citations (10 units)
curl -s -X POST "https://api.regsignal.dev/v1/ask" \
-H "X-API-Key: $REGSIGNAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "Is BPA banned in food contact materials in the EU?", "filter": {"jurisdictions": ["EU"]}, "top_k": 10}'
# 200 OK: answer, citations, insufficient_evidence, confidence, contexts, modelKey endpoints
Six of the 98 paths. Units are what each successful call costs against your monthly ceiling; the full list is in the OpenAPI spec.
| Endpoint | What it does | Units |
|---|---|---|
GET/v1/signals | The signal feed, filtered by jurisdictions, verticals, topics, doc types and dates, with keyset pagination. | 1 |
GET/v1/signals/{signal_id} | One signal. Add include_text=true for the extracted full text. | 1 |
POST/v1/ask | A question answered from the corpus, with citations and an insufficient_evidence flag. | 10 |
POST/v1/profiles/{profile_id}/impact/run | Score signals against one of your product profiles and store the matches. | 20 |
GET/v1/profiles/{profile_id}/impact | The stored impact matches for a profile, filtered by min_level. | 1 |
POST/v1/webhooks | Register a webhook endpoint. The signing secret is returned once, in this response. | 1 |
Errors
The TypeScript and Python SDKs turn these into typed errors and retry 429 and 5xx responses with backoff, honouring Retry-After.
| Status | When | Body |
|---|---|---|
| 400 | A malformed pagination cursor | "detail": "invalid cursor" |
| 401 | No credential, or an API key that does not exist or was revoked | "detail": "invalid API key" |
| 403 | The key’s role cannot do this, or a seat, profile or webhook ceiling is reached | "code": "plan_limit_reached" for plan ceilings |
| 404 | The resource does not exist | detail |
| 413 | The request body is larger than 2,000,000 bytes | "code": "request_too_large", limit_bytes |
| 422 | The request failed validation | detail: a list of loc, msg, type |
| 429 | The daily request or monthly unit ceiling is used up | "code": "plan_limit_exceeded", resets_at, and a Retry-After header |
Rate limits and plan ceilings
Ceilings are per organization and are checked by the API before a request runs. Requests count /v1/* responses below 400. Units are weighted: ask 10, impact runs and profile analysis 20, classify, impact match and signal ingest 5, search 2, everything else 1. /v1/health, /v1/me and /v1/billing/* are never limited. Read your plan, ceilings and usage with GET /v1/billing/plan.
| Limit | Free | Developer | Enterprise |
|---|---|---|---|
requests_per_dayresets 00:00 UTC | 200 | 5,000 | No ceiling |
units_per_monthresets on the 1st, UTC | 2,000 | 100,000 | No ceiling |
profilesproduct profiles | 3 | 50 | No ceiling |
webhooksactive endpoints | 1 | 10 | No ceiling |
seatsmembers and pending invitations | 2 | 10 | No ceiling |
Webhook signatures
Verify every delivery
Each delivery is signed with your endpoint’s secret. Check the signature and the timestamp before you trust the body.
- Header
X-RegSignal-Signature: t=<unix>,v1=<hex>- Algorithm
- HMAC-SHA256 keyed with the endpoint secret, hex-encoded.
- Signed bytes
- The timestamp, a dot, then the raw request body:
"<t>." + raw body. Verify the bytes you received, not a re-serialized object. - Tolerance
- The SDK verifiers reject a timestamp more than 300 seconds from now, so a captured delivery cannot be replayed later. Digests are compared in constant time.
- Secret
- Starts with
whsec_. Returned once when you create the webhook and again fromPOST /v1/webhooks/{webhook_id}/rotate-secret. - Events
signal.created,signal.updated,impact.matched, andpingfromPOST /v1/webhooks/{webhook_id}/test. The body is{id, type, created_at, data}.
- Any 2xx counts as delivered. Redirects are not followed; each attempt times out after 10 seconds.
- A failed delivery is tried up to 8 times, waiting at least 1, 2, 4, 8, 16, 32 and 64 minutes between attempts.
- Every attempt is logged in
GET /v1/webhooks/{webhook_id}/deliveriesand can be resent with the delivery’sretryendpoint. - An endpoint is switched off after 100 failed attempts in a row.
import express from 'express';
import { constructEvent, SignatureVerificationError } from '@regsignal/sdk/webhooks';
const app = express();
// express.raw keeps the exact bytes: the signature covers the body as sent
app.post('/regsignal/webhook', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = constructEvent(
process.env.REGSIGNAL_WEBHOOK_SECRET!,
req.header('X-RegSignal-Signature') ?? '',
req.body as Buffer,
);
} catch (err) {
if (err instanceof SignatureVerificationError) return res.status(400).send('invalid signature');
throw err;
}
if (event.type === 'impact.matched') {
console.log(event.data.impact.level, 'for profile', event.data.profileId);
}
res.sendStatus(200);
});from fastapi import FastAPI, Header, HTTPException, Request
from regsignal import SignatureVerificationError, webhooks
app = FastAPI()
@app.post("/regsignal/hook")
async def hook(request: Request, x_regsignal_signature: str = Header(None)):
body = await request.body() # raw bytes, before any JSON parsing
try:
event = webhooks.construct_event(WEBHOOK_SECRET, x_regsignal_signature, body)
except SignatureVerificationError:
raise HTTPException(status_code=400, detail="bad signature")
if event["type"] == "impact.matched":
handle_impact(event["data"]["impact"], event["data"]["profile_id"])
return {"ok": True}Delivery headers
Both SDKs also export verifySignature / verify_signature, which return a boolean instead of throwing, and sign for building test fixtures.
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | RegSignal-Webhooks/1.0 |
X-RegSignal-Event | The event type, for example impact.matched |
X-RegSignal-Delivery | The delivery id; the same id on every retry of one delivery |
X-RegSignal-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
TypeScript SDK
TypeScript and JavaScript
@regsignal/sdk wraps the Signal, Intelligence and Action APIs with full types for every request and response.
npm install @regsignal/sdk- Node 18+ and modern browsers. Uses the global
fetch, with zero runtime dependencies. - ESM and CommonJS builds. camelCase in TypeScript, snake_case on the wire.
- Retries 429, 5xx and network errors with backoff (
maxRetries, default 2) and honoursRetry-After. - Typed errors:
AuthenticationError,NotFoundError,ValidationError,RateLimitError,ServerError. @regsignal/sdk/webhooksverifies signatures (Node only, usesnode:crypto).
Invite-only soft launch: the package is not on the public npm registry yet. The name, install command and client API shown here come from the SDK’s own source and README.
import { RegSignal, RateLimitError } from '@regsignal/sdk';
const client = new RegSignal({ apiKey: process.env.REGSIGNAL_API_KEY! });
// Walk the feed; iterate() follows next_cursor for you
for await (const signal of client.signals.iterate({ jurisdictions: ['EU'], docTypes: ['regulation'] })) {
console.log(signal.id, signal.title, signal.updatedAt);
}
// Ask with citations
try {
const answer = await client.intelligence.ask({
question: 'Is BPA banned in food contact materials in the EU?',
filter: { jurisdictions: ['EU'] },
topK: 10,
});
console.log(answer.answer, answer.citations.map((c) => c.url));
} catch (err) {
if (err instanceof RateLimitError) console.error('retry after', err.retryAfter, 's');
else throw err;
}Python SDK
Python
regsignal has sync and async clients with the same surface and typed responses.
pip install regsignal
# or
uv add regsignal- Python 3.10+, one dependency:
httpx. RegSignalandAsyncRegSignal, both usable as context managers.- Dataclass responses; fields the SDK does not know yet are kept in
.raw. - Retries 429, 5xx and connection errors with exponential backoff and jitter, honouring
Retry-After. regsignal.webhooks.verify_signatureandconstruct_eventmatch the server’s reference verifier.
Invite-only soft launch: the package is not on PyPI yet. The name, install command and client API shown here come from the SDK’s own source and README.
from regsignal import RegSignal
client = RegSignal(api_key="rk_live_...") # sent as X-API-Key
# Walk the feed; iterate() follows next_cursor for you
for signal in client.signals.iterate(jurisdictions=["EU"], limit=100):
print(signal.jurisdiction, signal.doc_type, signal.title)
# Ask with citations
answer = client.intelligence.ask(
"Is BPA banned in food contact materials in the EU?",
filter={"jurisdictions": ["EU"]},
top_k=10,
)
if answer.insufficient_evidence:
print("Not enough evidence in the corpus.")
else:
print(answer.answer)
for c in answer.citations:
print(c.title, c.url)OpenAPI
The whole API in one spec
The API serves its own OpenAPI document and an interactive reference. Use the spec to browse every route or to generate a client with the tooling you already have.
/v1# Fetch the spec and count its paths
curl -s https://api.regsignal.dev/openapi.json | jq '.paths | length'
98MCP server
RegSignal as tools for agents
A remote MCP server lets Claude, ChatGPT, Cursor and other MCP hosts query the corpus with your API key.
- Endpoint
https://mcp.regsignal.dev/mcp, Streamable HTTP, stateless.- Authentication
Authorization: Bearerwith your API key, orX-API-Key. The key keeps its organization, plan and analyst role.- Tools
- 11 read-only tools. None of them create, change or delete anything.
list_signalssearch_signalsget_signalaskmatch_impactlist_profilesprofile_impactslist_inboxlist_jurisdictionsget_taxonomylist_sources - Also
- Resources
regsignal://taxonomyandregsignal://jurisdictions, and aregulatory_briefprompt. - Metering
- Each tool call is one REST request, counted against your plan like any other;
askcosts 10 units.
# Claude Code
claude mcp add --transport http regsignal https://mcp.regsignal.dev/mcp \
--header "Authorization: Bearer $REGSIGNAL_API_KEY"
# Cursor: .cursor/mcp.json
{
"mcpServers": {
"regsignal": {
"url": "https://mcp.regsignal.dev/mcp",
"headers": { "Authorization": "Bearer rk_live_..." }
}
}
}