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”Calendar dates (tenderOpenAt, quotesDueAt, estimatedStartDate, estimatedEndDate) have no time component and are formatted as ISO 8601 YYYY-MM-DD, e.g. 2026-04-06. They may be null when not set (e.g. a tender with no due date).
Timestamps (createdAt) are RFC 3339 in UTC, to the second, e.g. 2026-05-15T03:04:05Z. Timestamps you send (createdSince) must be RFC 3339 with an offset (Z or e.g. +10:00); fractional seconds are ignored, because timestamps are stored to the second.
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.
Incremental sync
Section titled “Incremental sync”To keep a copy of results up to date without re-reading everything, filter by creation time: the Supplier API’s createdSince returns only projects added on or after an instant, oldest first, and pages like any other list. The filter is inclusive (>=) and timestamps are second-precision, so overlap each window with the last and de-duplicate by ID. It finds new projects, not new tenders on existing ones, so it supplements a regular full sync rather than replacing it. See Incremental CRM sync for the full pattern and its limits.
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.