Certigence Data API

Read-only programmatic access to ESG company data, ratings, and search. Authenticate with a `cer_...` API key provisioned from your Certigence dashboard. API keys and outbound webhooks require a Professional or Enterprise subscription. Rate limits per key: 100 requests per 15 minutes on Professional, 1,000 on Enterprise; exceeding them returns HTTP 429. There are no write endpoints.

Authentication

Create an API key in your Certigence dashboard and send it as Authorization: Bearer cer_... or in the X-API-Key header. Keys are tied to your company and require a Professional or Enterprise subscription; other plans receive HTTP 403. Keys are shown once and stored only as SHA-256 hashes; revoke a key in the dashboard and it stops working immediately.

Quick start

Base URL: https://app.certigence.eu. All endpoints are GET requests that return JSON. /companies is paginated with page and limit (at most 100 per page) and returns a pagination object with total and totalPages; the other list endpoints take only limit, capped per endpoint (50 for search, 100 for controversies, 200 for rankings, 3,650 for score history).

curl "https://app.certigence.eu/api/v1/companies?country=Norway&limit=20" \
  -H "Authorization: Bearer $CERTIGENCE_API_KEY"
const response = await fetch(
  "https://app.certigence.eu/api/v1/companies/123/score/history?interval=monthly",
  { headers: { "X-API-Key": process.env.CERTIGENCE_API_KEY } },
);
if (response.status === 429) {
  const { retryAfter } = await response.json();
  throw new Error(`Rate limited, retry in ${retryAfter}s`);
}
const { data } = await response.json();

Rate limits

  • Professional: 100 requests per 15 minutes per API key.
  • Enterprise: 1,000 requests per 15 minutes per API key.
  • Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds).
  • Requests over the limit receive HTTP 429 with retryAfter in seconds and a Retry-After header.

Endpoints

  • GET /api/v1/companies — List companies with scores
  • GET /api/v1/companies/{id} — Get a single company
  • GET /api/v1/companies/{id}/score — Get the current ESG score for a company
  • GET /api/v1/companies/{id}/controversies — Structured ESG controversies
  • GET /api/v1/companies/{id}/score/history — Historical ESG score snapshots
  • GET /api/v1/rankings/{sector} — Sector-level ranking for portfolio screening
  • GET /api/v1/search — Search companies by name

Errors

Errors are JSON with an error code and a human-readable message.

StatuserrorWhen it happens
400bad_requestA parameter is invalid, for example a company ID that is not a number or a search query shorter than 2 characters.
401unauthorizedThe key is missing, malformed (it must start with cer_), unknown, revoked or expired.
403forbiddenThe key belongs to a company without a Professional or Enterprise subscription.
404not_foundThe company does not exist, or it has no published score yet. An unknown sector in /rankings returns 200 with an empty list.
429Rate limit exceeded. …Too many requests for this key in the current 15-minute window. The error field holds the limit message, the body carries retryAfter in seconds and the Retry-After header is set.
500internal_error or InternalServerErrorSomething failed on our side; retry later. internal_error comes from key validation, InternalServerError from the endpoint itself.
{
  "error": "forbidden",
  "message": "API access requires a Professional or Enterprise subscription",
  "upgradeUrl": "https://certigence.eu/en/payment/subscription"
}

Webhooks

Outbound webhooks are signed with HMAC-SHA256 using the secret shown once when the endpoint is created (or rotated). The `Certigence-Signature` header has the form `t=<unix seconds>,v1=<hex>`; v1 is `hmac_sha256(secret, "<t>.<raw body>")`. Compare in constant time and reject timestamps more than 300 seconds old. The body is `{id, type, createdAt, companyId, data}`; id is stable across retries. Event types: company.updated, company.score_changed, alert.triggered, goal.achieved, goal.at_risk, deadline.upcoming, deadline.overdue, task.completed, action_plan.completed, verification.completed, payment.succeeded, payment.failed.

Create endpoints under Webhooks in the dashboard (Professional and Enterprise). The URL must be HTTPS and reachable from the public internet; private, loopback, link-local and metadata addresses are rejected, and redirects are not followed.

Each delivery is a JSON POST with id, type, createdAt, companyId and data. The id (evt_…) stays the same on every retry, so use it to ignore duplicates. Deliveries also carry X-Webhook-Event, X-Webhook-Event-Id, X-Webhook-Delivery-Id, X-Webhook-Attempt and X-Webhook-Timestamp, plus a legacy X-Webhook-Signature (HMAC-SHA256 of the raw body without a timestamp) for receivers built before the timestamped signature.

Endpoints have 15 seconds to answer with a 2xx status. Anything else, a timeout or a redirect is a failure. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours: 6 retries, the default. An endpoint can be set to 0 to 8 retries; retries 7 and 8 wait 24 hours each. After the last retry the delivery is dead-lettered, and you can redeliver it from the delivery log.

  • company.updated: someone on your team changed the company profile
  • company.score_changed: once a day, when your overall ESG score differs from the previous daily snapshot
  • alert.triggered: an alert rule with the Webhook channel fired
  • goal.achieved: a goal reached its target
  • goal.at_risk: a goal fell behind its expected progress
  • deadline.upcoming: a compliance deadline or CSRD action item is due within 7 days (once per item and due date)
  • deadline.overdue: a compliance deadline or CSRD action item passed its due date without being completed (once per item and due date)
  • task.completed: a task in an action plan was marked complete
  • action_plan.completed: every task in an action plan is done, or the plan was marked complete
  • verification.completed: Certigence verified one of your evidence documents
  • payment.succeeded: a subscription invoice was paid
  • payment.failed: a subscription invoice payment failed
{
  "id": "evt_1842",
  "type": "goal.achieved",
  "createdAt": "2026-10-25T09:30:00.000Z",
  "companyId": 42,
  "data": {
    "goalId": 7,
    "name": "Cut Scope 2 emissions by 40%",
    "status": "achieved",
    "previousStatus": "on_track",
    "progressPercentage": 100
  }
}
import crypto from "node:crypto";

export function verifyCertigenceWebhook(secret, rawBody, header, toleranceSeconds = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((part) => part.trim().split("=", 2)),
  );
  const timestamp = Number(parts.t);
  if (!Number.isInteger(timestamp) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  return (
    expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
  );
}

Data retention

  • Score history is kept as daily snapshots for at least five years; query it with from, to and interval=monthly.
  • Company and ESG data stay available for as long as the company profile exists.
  • Webhook events and delivery records are kept for 90 days and then deleted automatically.

What the API does not do

  • There are no write endpoints; data is entered in the app or through the supplier portal.
  • There are no official SDKs; generate a client from the OpenAPI spec or use any HTTP client.
  • There are no native CRM or ERP connectors; pull data from the API into those tools yourself.