Skip to main content

Reporting events

POST /v1/reports files a first-hand, structured report of an event that already happened: your company closed a funding round, signed a partnership, shipped a launch. You send the event type, when it happened, and a value for each of the event type's roles. Evinor takes the report as submitted, with no screening or extraction step, and publishes it under the name of the account that owns the API key.

Reports are published as you, immediately

A report is attributed to the key owner as their own first-hand account, and it enters Evinor's event pipeline as soon as it is accepted. There is no draft state and no retraction endpoint yet. Only file facts you have confirmed yourself, and give the write scope only to integrations you trust to do that.

Six endpoints make up the reporting surface:

EndpointScopeWhat it does
GET /v1/event-typesREPORTS_READThe event types you can report, with each role's expected type.
GET /v1/reporting-grantsREPORTS_READYour reporting grants: the grant_id values you can file under.
GET /v1/entities/search?query=REPORTS_READFind the entity_id to send for an entity role.
POST /v1/reportsREPORTS_WRITEFile a report.
GET /v1/reportsREPORTS_READYour filed reports, newest first (paginated).
GET /v1/reports/{id}REPORTS_READOne of your reports. Returns 404 for an id that isn't your report.

A key that files reports needs both scopes: without REPORTS_READ it cannot look up event types or entity ids, so it cannot build a valid report. The key dialog in Settings → API keys ticks REPORTS_READ for you when you tick REPORTS_WRITE. See Authentication.

Reporting is not billed. It does not draw on your credit balance.

Entitlement​

A scope decides which endpoints a key may call. Whether the account may actually file a report is checked separately, on every call, against the key owner's current plan:

  • Filing as yourself (no grant_id) needs structured reporting on the account.
  • Filing under a grant_id needs entity reporting on the account.

REPORTS_WRITE can only be given to a key whose owner has at least one of the two. If the owner later loses the entitlement, the key keeps its scope, but POST /v1/reports returns 403 not-entitled until the entitlement is restored.

Step 1: pick an event type​

GET /v1/event-types lists every event type that is currently accepting reports. The list is not paginated. The values below are illustrative; read the real event types from the endpoint.

{
"data": [
{
"id": "0b6f0d8e-3a7c-4d2e-9f1a-5c8e2b7d4a10",
"object": "event_type",
"code_name": "FUNDING_ROUND",
"name": "Funding round",
"description": "A company raised capital from investors.",
"roles": [
{
"code_name": "RECIPIENT",
"name": "Recipient",
"expected_type": "NAMED_ENTITY"
},
{ "code_name": "AMOUNT", "name": "Amount", "expected_type": "CURRENCY" },
{ "code_name": "ROUND", "name": "Round", "expected_type": "NONE" }
]
}
],
"has_more": false,
"next_cursor": null
}

Each role's code_name is what you send as roles[].role. Its expected_type decides which value field you send for that role (see the table below). An expected_type of null is a plain-text role.

Step 2: send one value per role​

POST /v1/reports takes this body:

FieldRequiredMeaning
event_type_idyesThe id of an event type from GET /v1/event-types.
happened_atyesWhen the event happened, as an ISO-8601 timestamp. Must not be in the future.
rolesyesAt least one role entry, at most 50. See below.
descriptionnoFree-text context, up to 5,000 characters after trimming. A blank value is dropped.
grant_idnoFile under one of your reporting grants.

Each entry in roles names a role by its code_name in role and carries exactly one value field, chosen by that role's expected_type:

expected_typeSendExample
NAMED_ENTITYentity_id: an entity id (UUID){ "role": "RECIPIENT", "entity_id": "7c1e…" }
DATEdate: a real calendar day, YYYY-MM-DD{ "role": "CLOSE_DATE", "date": "2026-09-14" }
CURRENCY, NUMBER, DISTANCE, WEIGHT, LENGTH, AREA, VOLUME, TEMPERATURE, SPEEDamount: { "value": <number>, "unit": <text> }{ "role": "AMOUNT", "amount": { "value": 25000000, "unit": "USD" } }
DURATION, NONE, or nulltext: a non-empty string{ "role": "ROUND", "text": "Series B" }
CATEGORICALNot supported yetAny value is rejected.

Rules that follow from the table:

  • Exactly one value field per role. Sending a field that doesn't match the role's type, or sending two, is a 400.
  • For CURRENCY, unit is an ISO 4217 currency code (USD, EUR, …). For the other measurable types unit is a unit label, and may be empty (for example, a plain count).
  • Send each role at most once. A role that appears twice in roles is a 400. You don't have to send every role, but the report must carry enough of them to describe the event (see SENTENCE_NOT_RENDERABLE under Errors).
  • Durations are text ("18 months"). A duration Evinor can't read is rejected.
  • CATEGORICAL roles can't be filed yet. Their allowed values are not published, so a report cannot fill them. Leave them out.
  • There is no date-time value. A DATE role takes a calendar day only.

Entity roles take an entity id, never a name​

An entity role must reference an entity that already exists in Evinor, by id. You cannot create an entity or name one in free text. This keeps a stream of name variants ("Acme", "Acme Inc.", "ACME Corp") from turning into duplicate entities.

Find the id with GET /v1/entities/search:

curl "https://api.evinor.ai/v1/entities/search?query=acme&limit=5" \
-H "Authorization: Bearer evnr_live_your_key_here"
{
"data": [
{
"id": "7c1e9a52-0d4b-4f6e-8a3c-2b9d5e1f7a64",
"object": "entity",
"name": "Acme Corporation",
"type": "ORGANIZATION"
}
],
"has_more": false,
"next_cursor": null
}

query is required (1–200 characters). The endpoint takes the usual limit and starting_after pagination parameters. Entities that have been merged into another entity are never returned, so an id from this endpoint is always a current one. Evinor records the entity's name on the report for you.

Filing a report​

curl -X POST https://api.evinor.ai/v1/reports \
-H "Authorization: Bearer evnr_live_your_key_here" \
-H "Idempotency-Key: 5d2f8c1a-9e7b-4c3d-a1f0-6b8e2d4c7a93" \
-H "Content-Type: application/json" \
-d '{
"event_type_id": "0b6f0d8e-3a7c-4d2e-9f1a-5c8e2b7d4a10",
"happened_at": "2026-09-14T00:00:00Z",
"roles": [
{ "role": "RECIPIENT", "entity_id": "7c1e9a52-0d4b-4f6e-8a3c-2b9d5e1f7a64" },
{ "role": "AMOUNT", "amount": { "value": 25000000, "unit": "USD" } },
{ "role": "ROUND", "text": "Series B" }
],
"description": "Led by Example Ventures."
}'

A successful filing returns 201 with the report resource. The TypeScript SDK also checks the report against the event type before sending it.

The report resource​

POST /v1/reports, GET /v1/reports/{id}, and each entry of GET /v1/reports return the same shape:

{
"id": "e4a1c7b2-8d3f-4e6a-9b0c-1f2d3e4a5b6c",
"object": "report",
"status": "CONFIRMED",
"event_type_id": "0b6f0d8e-3a7c-4d2e-9f1a-5c8e2b7d4a10",
"happened_at": "2026-09-14T00:00:00.000Z",
"roles": [
{ "role": "RECIPIENT", "entity_id": "7c1e9a52-0d4b-4f6e-8a3c-2b9d5e1f7a64" },
{ "role": "AMOUNT", "amount": { "value": 25000000, "unit": "USD" } },
{ "role": "ROUND", "text": "Series B" }
],
"description": "Led by Example Ventures.",
"grant_id": null,
"published_as": { "name": "Jane Doe", "kind": "INDIVIDUAL" },
"incident_id": null,
"incident_report_id": null,
"created_at": "2026-09-25T18:02:11.412Z"
}
FieldMeaning
statusA filed report is CONFIRMED: accepted as submitted.
rolesThe same form you send. A value that hasn't been resolved to its typed form yet reads back as text.
grant_idThe grant the report was filed under, or null.
published_asThe public name the report is attributed to, as { name, kind }. name is your account's display name, or the grant's label for a report filed under a grant. kind is INDIVIDUAL or ORGANIZATION (filed under a grant). null for reports filed before attribution existed.
incident_idThe event this report contributed to. null until Evinor has processed the report, which happens shortly after filing.
incident_report_idThe report's entry in Evinor's event pipeline. null until processed.

event_type_id and happened_at are nullable in the schema; treat a null as unknown.

GET /v1/reports and GET /v1/reports/{id} return only reports filed through this structured flow. GET /v1/reports/{id} answers 404 not-found for an id that doesn't exist, belongs to another account, or isn't a structured report. All three get the same response.

Filing under a reporting grant​

A reporting grant lets an account file reports for an entity it represents, such as an agency filing on behalf of a client. GET /v1/reporting-grants lists yours (not paginated):

{
"data": [
{
"id": "3a9d2c7e-1b4f-4e8a-9c6d-0f5e2a7b8c1d",
"object": "reporting_grant",
"label": "Acme press office",
"status": "ACTIVE",
"is_stale": false,
"expires_at": "2027-01-01T00:00:00.000Z",
"event_type_id": "0b6f0d8e-3a7c-4d2e-9f1a-5c8e2b7d4a10",
"pins": [
{
"role_definition_id": "b2c41f7a-6e0d-4a93-8c5b-7d1e2f3a4b5c",
"role": "RECIPIENT",
"allowed_entities": [
{
"id": "7c1e9a52-0d4b-4f6e-8a3c-2b9d5e1f7a64",
"object": "entity",
"name": "Acme Corporation",
"type": "ORGANIZATION"
}
]
}
]
}
],
"has_more": false,
"next_cursor": null
}

To file under a grant, pass its id as grant_id:

  • The report's event_type_id must be the grant's event_type_id.
  • Each pin fixes a role to a set of entities. If you send that role, its entity_id must be one of allowed_entities. If a pin allows exactly one entity and you leave the role out, Evinor fills it in; if it allows several, you must send it. Anything else is refused with PIN_VIOLATION.
  • A grant with is_stale: true no longer matches its event type (for example, the event type changed after the grant was issued) and cannot be used. Its pins may carry role: null.

The list is empty for an account without entity reporting.

Retries and idempotency​

Send an Idempotency-Key with every POST /v1/reports. On this endpoint the key does more than usual: Evinor derives the report's id from your API key and the idempotency key, so one idempotency key always identifies one report.

You retry with the same key…Result
within 24 hours of a successful first attemptThe original 201 is replayed, with Idempotency-Replayed: true. No second report.
while the first attempt is still running409 idempotency-in-flight.
after the first attempt failed with a malformed body (400 with errors)The key was never used. The request runs.
after any other failed first attempt409 idempotency-in-flight for about a minute, then the request runs again.
after the 24-hour replay window, or after a first attempt whose response you lost409 report-already-submitted if the report was filed. No second report.

The last row is the protection: even when the stored response is gone, the same key can never file the same report twice. The 409 body does not include the report id; find the report with GET /v1/reports.

Use a new key for each genuinely new report. Without an Idempotency-Key, every request files a new report, so a blind retry after a timeout can publish the same report twice.

Rate limits​

POST /v1/reports counts against the same per-key budget as every other REST call, reads included: 60 requests per minute per key by default. See Rate limits. Calls made through the MCP server draw on the separate MCP budget instead.

Errors​

A malformed body (a missing field, a non-UUID entity_id, a date not in YYYY-MM-DD form, an unknown field) is 400 validation-failed with the usual errors array. See Errors.

When Evinor refuses a report because of what is in it, the 400 carries a stable code member instead, which you can branch on:

{
"type": "https://docs.evinor.ai/problems/validation-failed",
"title": "Validation failed",
"status": 400,
"detail": "Role \"AMOUNT\" expects exactly one `amount` value for its CURRENCY type.",
"request_id": "…",
"code": "ROLE_VALUE_TYPE_MISMATCH"
}
codeMeaning
TEMPLATE_NOT_FOUNDNo event type matches event_type_id.
TEMPLATE_INACTIVEThe event type is not accepting reports.
NO_ROLESroles is empty.
DESCRIPTION_TOO_LONGdescription is over 5,000 characters after trimming.
UNKNOWN_ROLE_CODE_NAMEA role is not one of the event type's code_names.
ROLE_NOT_MULTI_VALUEDA role appears more than once.
UNSUPPORTED_ROLE_TYPEA role is CATEGORICAL.
ROLE_VALUE_TYPE_MISMATCHA role sends the wrong value field, or more than one.
INVALID_DATEA date is not a real calendar day.
EMPTY_TEXT_VALUEA text value is blank.
HAPPENED_AT_INVALIDhappened_at is not a valid ISO-8601 timestamp.
HAPPENED_AT_IN_FUTUREhappened_at is in the future.
UNKNOWN_NAMED_ENTITYAn entity_id does not exist.
SENTENCE_NOT_RENDERABLEThe roles sent are not enough to describe this event type. Add the roles it needs.
GRANT_STALEThe grant no longer matches the event type.
PIN_VIOLATIONThe roles don't satisfy the grant's pins.
REPORTER_NAME_MISSINGThe API key owner has no display name. Set one on your account profile first; reports filed under a grant use the grant's label instead.

A few more codes guard size limits and internal consistency, and are listed here so a client can recognise them: TOO_MANY_ROLES, TEXT_VALUE_TOO_LONG, PARSED_AMOUNT_UNIT_TOO_LONG, PARSED_DATE_TOO_LONG, PARSED_DATE_TIME_TOO_LONG, NAMED_ENTITY_ID_ON_NON_ENTITY_ROLE, and DUPLICATE_ROLE_ID.

The other problems specific to this endpoint:

StatusProblemcodeMeaning
403not-entitledFORBIDDEN, GRANT_FORBIDDEN, GRANT_NOT_USABLEThe account can't file reports, can't file under grants, or can't use this grant_id. The response doesn't say why a grant is unusable.
409report-already-submittednoneThis idempotency key already filed a report.
502upstream-errornoneThe report could not be filed for another reason. Retry with the same Idempotency-Key, so a report that was filed isn't filed twice.