Conventions
Shared conventions that apply across all External API endpoints.
Content type
Section titled “Content type”All requests and responses are JSON (application/json). The API does not accept or produce XML, form-encoded bodies on read endpoints, or any other content type.
The one exception is the OAuth2 token endpoint itself, which follows the OAuth2 standard and accepts application/x-www-form-urlencoded. See Authentication.
Response envelope
Section titled “Response envelope”Successful responses wrap their payload in a data field.
Single resource:
{ "data": { "id": "123", "name": "..." } }List resource:
{ "data": [ { ... }, { ... } ] }This envelope is consistent regardless of endpoint. The shape inside data varies per endpoint — see the Supplier API reference for specifics.
Error envelope
Section titled “Error envelope”Errors use a separate shape with an _errors array:
{ "_errors": [ { "code": "CLIENT_ID_HEADER_MISSING", "detail": "Missing required x-e1-client-id header" } ]}Validation errors may additionally include a source.parameter to point at the offending field:
{ "_errors": [ { "code": "INVALID_PARAMETER", "detail": "companyId must be a numeric string", "source": { "parameter": "companyId" } } ]}The full list of error codes lives in Errors.
Dates and times
Section titled “Dates and times”The Supplier API exclusively uses dates (no time component) on response fields (tenderOpenAt, quotesDueAt), formatted as ISO 8601 YYYY-MM-DD, e.g. 2026-04-06.
Date fields may be null when not set (e.g. a tender with no due date). Future endpoints that return full timestamps will use ISO 8601 with UTC offset (e.g. 2026-05-15T00:00:00+00:00); none are exposed today.
Identifiers
Section titled “Identifiers”- Identifiers are strings, even when they look numeric. Don’t assume
parseIntis safe — treat IDs as opaque tokens. companyIdvalues are assigned by E1 and stable across the lifetime of the company.
Pagination
Section titled “Pagination”List endpoints use cursor-based pagination. A paginated response carries a _metadata object alongside data:
{ "data": [ { ... }, { ... } ], "_metadata": { "nextCursor": "MjAyNi0wNS0xNXwxMjM0NQ", "hasNextPage": true }}To page through results:
- Make the first request without a
cursor(optionally setlimit, which defaults to25and is capped at100). - While
_metadata.hasNextPageistrue, repeat the request withcursorset to the previous response’s_metadata.nextCursor. - Stop when
hasNextPageisfalse(nextCursorisnull).
The cursor is opaque — treat it as a token and don’t construct, parse, or persist its internal form. It encodes a position in the result ordering, so pages never overlap or skip entries even if you change limit between requests. Re-requesting the same cursor/limit within a short window returns a cached page.
See the Supplier API reference for the per-endpoint ordering and parameters.
HTTP status codes
Section titled “HTTP status codes”| Status | Meaning |
|---|---|
200 OK | Request succeeded. Body contains the data envelope. |
400 Bad Request | The request was malformed — missing required header, invalid parameter, etc. |
401 Unauthorised | Authentication or authorization failed. See Errors. |
403 Forbidden | The supplier company you are calling for is not licensed for this endpoint. See Errors. |
429 Too Many Requests | Rate limit exceeded for your (token, x-e1-client-id) bucket. See Rate limits. |
5xx | Something broke on our side. Retry with exponential backoff; escalate if it persists. |
Note that the API will sometimes return 200 OK with an empty data: [] — that is not an error. It means your query ran successfully but there were no matching projects. See Data freshness for why that might be.