Skip to content

A product of FEEC

feec.asia

Developers · Enterprise

Connect your systems to your emissions inventory.

EmitLedger's partner API lets entitled tenants read their assets, reporting periods and aggregated emissions, push meter readings, events and site-level methane measurements from their own systems or vendors, and receive signed webhooks. Pushed data goes through the same validation as the web app.

Scope

Scoped keys, one tenant each.

Read, write or both

Each key has scopes: read, write:activity, write:events and write:measurements. A key with only read is read-only; a vendor that only pushes data can get a write-only key. Existing keys keep read.

The tenant sets the boundary

A key only reaches its own tenant's data. Writes act with the authority of the administrator who created the key: their role and asset scope apply, and writes stop if that person leaves the organisation or loses the right to enter data.

Enterprise entitlement

API access and webhooks are included in Enterprise. A plan without the module receives an entitlement response instead of data.

Authentication

Create a secret once. Store it safely.

A client administrator creates, lists and revokes keys in Settings → API keys, or through the key-management API: a name, the scopes and an optional expiry date. The secret is returned once; only its SHA-256 hash is stored. A lost key must be revoked and replaced. Scopes are fixed when the key is created.

Key management routes

  • GET /api/v1/api-keys — list name, prefix, scopes, status and last use.
  • POST /api/v1/api-keys with {"name":"data platform","expires_at":"2027-01-01T00:00:00Z"} and a scopes list (default read; expiry optional) — create; copy the returned secret immediately.
  • DELETE /api/v1/api-keys/{key_id} — revoke.

Through the API, use an authenticated client-administrator request with the tenant context. An expired or revoked key is refused. Never put a key in source control, a URL or a browser application.

Send it in a header

Use either standard bearer syntax or the dedicated header:

curl --fail-with-body \
  -H "Authorization: Bearer $API_KEY" \
  https://api.emitledger.com/api/v1/partner/assets

Read

Start with assets, sources, periods or results.

GET /api/v1/partner/assets

Returns the tenant's assets: id, code, name, type, operator, operated status, dates, country and row version.

GET /api/v1/partner/sources

Emission sources with their data streams: the source and stream ids and the stream units the write endpoints expect. Optional asset_id.

GET /api/v1/partner/periods

Optional filters: asset_id, period_from and period_to in YYYY-MM form. Includes governance status and timestamps.

GET /api/v1/partner/results

Requires period_from and period_to. Optional interval=month|quarter|year, split_by=scope|category|gas|asset, asset_id, gwp=AR4|AR5|AR6, consolidation=operational|equity and scope2=location|market. Omitted accounting options use the tenant defaults.

curl --fail-with-body \
  -H "X-API-Key: $API_KEY" \
  "https://api.emitledger.com/api/v1/partner/results?period_from=2025-01&period_to=2025-12&interval=month&split_by=scope"

Aggregated results use the same dashboard service as the web app. All read routes need the read scope.

GET /api/v1/partner/activity

The current value of every stream and month, with its meter tag, source tag, origin and correction count, to reconcile your historian or ERP against EmitLedger. Optional asset_id, stream_id, period_from, period_to.

GET /api/v1/partner/result-lines

Calculated tonnes per source, month and gas, with the method and every factor used (code, value, unit, whether verified), so a total can be traced to its inputs. Optional asset_id, source_id, period_from, period_to.

Write

Push data through the same checks as the web app.

Each write route takes 1 to 200 items and answers with one result per item: created, unchanged or rejected with the validation code the web app and the Excel import use. A rejected item never blocks the others.

POST /api/v1/partner/activity

Scope write:activity. Monthly values per data stream. The unit must be the stream's unit and is stored as sent; locked months are refused, approved months reopen, and a recalculation is queued.

POST /api/v1/partner/events

Scope write:events. Flaring, venting, blowdown and well events with the fields and checks of the web app's event form; a recalculation is queued.

Production, compositions, instruments, counts, LDAR

Scope write:activity. POST /api/v1/partner/production, /compositions, /energy-instruments, /inventory-counts and /ldar-findings take the fields and checks of the matching web form, with the same per-item results.

POST /api/v1/partner/imports

Scope write:imports. Upload the filled Excel template (or a mapped file), follow its validation at GET /partner/imports/{id} and commit it with POST /partner/imports/{id}/commit: the same checks and findings as the Excel page.

POST /api/v1/partner/site-measurements

Scope write:measurements. Site-level methane measurements (aerial, satellite, continuous monitoring) for measurement reconciliation. They do not change the inventory.

Safe to retry

Every item carries your external_id, unique per tenant and record type. Sending the same item again returns unchanged; reusing an id with different content is refused (IDEMPOTENCY_CONFLICT), so corrections go under a new id and become a new version. Every write is in the tenant's audit log under the key.

results.N.index         position of the item in the request
results.N.external_id   your id, echoed back
results.N.status        created | unchanged | rejected
results.N.code          e.g. E012 (unit), E006 (locked month), DUPLICATE
results.N.errors        field-level details of a rejection
calc_run_ids            recalculations queued for the affected assets

Example

curl --fail-with-body -X POST \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  https://api.emitledger.com/api/v1/partner/activity \
  -d '{"items": [{"external_id": "scada:FT-101:2025-06",
                  "stream_id": "id from /partner/sources", "period": "2025-06",
                  "value": 18250, "unit": "Sm3"}]}'

Webhooks

Get told instead of polling.

Client administrators register HTTPS endpoints in Settings → Webhooks, choose the events and receive a signing secret once. Payloads carry ids and states only; read the details with a read key.

Events

  • period.approved, period.locked — a reporting month was approved or locked.
  • results.recalculated — a calculation run changed results.
  • import.committed — an Excel or mapped import was committed.
  • reconciliation.decided — a measurement reconciliation decision was recorded.
  • ldar.overdue — the weekly LDAR reminder found overdue surveys or repairs.
  • period.submitted, period.rejected — a month was submitted for review or sent back.
  • verification.rfi.raised, verification.rfi.answered — a verifier's request for information was raised or answered.
  • report.ready — a requested report or export can be downloaded.
  • import.failed — an import failed validation or could not be processed.
  • restatement.approved — a restatement was approved and its months reopened.
  • webhook.test — the test ping from the settings page.

Verify every request

Each request carries X-Webhook-Id, X-Webhook-Event, X-Webhook-Timestamp and X-Webhook-Signature, an HMAC-SHA256 of the timestamp and the raw body with your secret.

expected = "v1=" + hmac_sha256(secret, timestamp + "." + raw_body).hex()
accept  = constant_time_equal(expected, X-Webhook-Signature)
          and abs(now - X-Webhook-Timestamp) <= 300

Answer with a 2xx within 10 seconds. Failed attempts are retried with exponential backoff, redirects are not followed, delivery is at least once (de-duplicate on X-Webhook-Id), and an endpoint that keeps failing is disabled until an administrator re-enables it. Endpoints must be public HTTPS addresses: an address that resolves to a private, loopback, link-local or cloud-metadata network is refused when the endpoint is registered and checked again before every delivery. The signing secret is shown once and can be rotated.

Operations

Know the limits and errors.

  • 401 API_KEY_REQUIRED — no key supplied.
  • 401 API_KEY_INVALID — unknown, revoked, expired or malformed key.
  • 402 ENTITLEMENT_REQUIRED — the tenant plan lacks API access.
  • 403 API_KEY_SCOPE_REQUIRED — the key lacks the route's scope.
  • 403 API_KEY_OWNER_INACTIVE — the key's creator is no longer a member (writes).
  • 403 TENANT_SUSPENDED — the tenant is not active.
  • 404 NOT_FOUND — an asset is outside the key's tenant.
  • 413 PAYLOAD_TOO_LARGE — a write body above 512 KiB.
  • 429 — rate limited; wait for Retry-After.
  • 422 BAD_CURSOR — a malformed paging cursor.

The asset, source and period lists are paged: each response has next_cursor; pass it back as cursor until it is null. limit sets the page size (up to 500, the default).

Version and rate limit

Routes are under /api/v1 and described in the OpenAPI document. Requests are counted per key: by default 120 requests per minute per key, of which at most 30 per minute may be writes. Contact FEEC before running high-volume integrations. Successful authorized calls update the key's last-used time; key creation and revocation, and every write through a key, are recorded in the tenant audit log.

Let's talk

Planning an integration?

Tell us what data you need to exchange and how often. We will confirm the current plan entitlement and rate limit before you build against the API.