Supplier API
The Supplier API exposes Speci-finder matched project results for a supplier user.
Open the live API reference
List Speci-finder results
Section titled “List Speci-finder results”GET /api/tender/supplier/v1/companies/{companyId}/speci-finder/resultsRequired
Section titled “Required”| Requirement | Value |
|---|---|
| Scope | speci-finder/read |
| Headers | Authorization: Bearer <access_token>, x-e1-client-id: <client_id> |
Path parameters
Section titled “Path parameters”| Name | Type | Description |
|---|---|---|
companyId | string | E1 company identifier for the supplier — the company account number shown in the E1 web app. Must match a registration on your application. |
Query parameters
Section titled “Query parameters”All are optional. Selection of “currently open” tenders is scoped server-side. createdSince narrows that selection to recently added projects; cursor and limit control paging.
| Name | Type | Description |
|---|---|---|
createdSince | RFC 3339 date-time | Only return projects added to E1 on or after this instant (inclusive), e.g. 2026-05-01T00:00:00Z. The UTC offset (Z or +10:00) is required; fractional seconds are ignored. When set, results are ordered by createdAt, oldest first. It matches on the project’s creation, so a new tender added to an existing project isn’t returned; a full sync picks that up. A malformed value returns 400. See Incremental CRM sync. |
cursor | string | Opaque pagination cursor. Pass the _metadata.nextCursor value from a previous response to fetch the following page. Omit on the first request; an empty value is treated the same as omitting it. Treat it as opaque — do not construct or parse it. |
limit | integer | Page size. Defaults to 25; must be between 1 and 100. An out-of-range or non-numeric value returns 400. |
Response (200 OK)
Section titled “Response (200 OK)”Returns one page of matched projects, each with one or more matched tenders, plus a _metadata object describing the page. A project appears once per response; if a single project has matched tenders under different builders, those tenders are grouped under the same project entry.
{ "data": [ { "projectId": "12345", "projectName": "Main Street Development", "createdAt": "2026-04-20T03:04:05Z", "address": "123 Collins St, Melbourne Victoria 3000 Australia", "category": "Commercial", "budget": { "low": 1000000, "high": 5000000 }, "status": "Active", "consultants": [ { "name": "Smith Engineers", "website": "https://smith.example", "types": ["ENGINEER_STRUCTURAL", "ENGINEER_CIVIL"] }, { "name": "Jones Architects", "website": null, "types": ["ARCHITECT"] } ], "tenders": [ { "id": "67890", "name": "Bulk Earthworks", "type": "Tender", "builder": { "name": "ABC Constructions", "website": "https://abc.example" }, "tenderOpenAt": "2026-05-01", "quotesDueAt": "2026-05-15", "description": "Bulk excavation of the site.\nIncludes retention and shoring.", "estimatedStartDate": "2026-07-01", "estimatedEndDate": "2027-06-30", "status": "Open", "matchedKeywords": ["concrete", "formwork"] } ], "tenderingSubcontractors": [ { "name": "Apex Concrete", "website": "https://apex.example" }, { "name": "Pour & Set", "website": null } ], "url": "https://app.estimateone.com/find-tenders?projectId=12345" } ], "_metadata": { "nextCursor": "MjAyNi0wNS0xNXwxMjM0NQ", "hasNextPage": true }}Response field reference
Section titled “Response field reference”Project
| Field | Type | Notes |
|---|---|---|
projectId | string | E1 project identifier. |
projectName | string | Human-readable project name. |
createdAt | RFC 3339 date-time (UTC) | When the project was added to E1, to the second, e.g. 2026-04-20T03:04:05Z. It doesn’t change when tenders are added to the project later; each tender’s tenderOpenAt says when that tender opened. |
address | string | Full international postal address (street, suburb, state, postcode, country). |
category | string | Project category (e.g. Commercial, Residential). |
budget | { low: number, high: number | null } | null | Banded budget range in dollars. null when no budget is set; high may be null for an open-ended top end. |
status | "Active" | "Closed" | Project-level status. Closed indicates the project itself is closed (regardless of any individual tender status). |
consultants | array of Consultant | Consultants engaged on the project. Empty for partners without the consultant-details add-on. Excludes unverified entries. |
tenders | array of Tender | One or more matching tenders. Never empty for a returned project. |
tenderingSubcontractors | array of Party | Subcontractors who have responded affirmatively to the builder on this project (currently quoting / quoted / quoted outside E1, or signalled interest via watchlist). Subject to the E1 SubbieSupplierConnection unlock model — for projects you haven’t unlocked, this lists only previously-viewed subbies. Deduplicated by company. |
url | string | Deep link into the E1 web app for this project. |
Tender
| Field | Type | Notes |
|---|---|---|
id | string | E1 tender identifier. |
name | string | Tender name (typically a trade package, e.g. “Bulk Earthworks”). |
type | "Tender" | "Procurement" | Which product surface the tender belongs to. |
builder | Party | The lead builder running this tender. |
tenderOpenAt | ISO 8601 date (YYYY-MM-DD) or null | When the tender opened. |
quotesDueAt | ISO 8601 date (YYYY-MM-DD) or null | When quotes close. null if no due date is set. |
description | string | null | The builder’s description of the works, as plain text with a line break (\n) between paragraphs and list items. Each builder writes their own, so it can differ between tenders on the same project. null when the builder hasn’t provided one. |
estimatedStartDate | ISO 8601 date (YYYY-MM-DD) or null | The builder’s estimated construction start date. null if not set. |
estimatedEndDate | ISO 8601 date (YYYY-MM-DD) or null | The builder’s estimated construction end date. null if not set. |
status | "Open" | "Closed" | "Awarded" | Tender-level status. |
matchedKeywords | array of string | The subset of the supplier account’s Speci-finder keywords that matched this tender. |
| Field | Type | Notes |
|---|---|---|
name | string | Display name of the company (preferring the linked E1 account’s name when available). |
website | string | null | Company website URL, normalised to include an https:// scheme. null when no website is on file. |
A Party plus the disciplines it’s engaged under on this project.
| Field | Type | Notes |
|---|---|---|
name | string | As for Party. |
website | string | null | As for Party. |
types | array of string | The consultant’s disciplines on this project, in E1’s ranking order. A firm engaged under several disciplines (say, architect and landscape architect) appears once and lists each one. One of: ARCHITECT, ARCHITECT_INTERIOR, ARCHITECT_LANDSCAPE, ENGINEER_ACOUSTIC, ENGINEER_CIVIL, ENGINEER_ELECTRICAL, ENGINEER_FIRE, ENGINEER_GEOTECHNICAL, ENGINEER_HYDRO, ENGINEER_MECH, ENGINEER_STRUCTURAL, ENGINEER_THERMAL, CONSULTANT_ACCESS, CONSULTANT_ARBORICULTURE, CONSULTANT_ENVIRONMENTAL, CONSULTANT_FIRE, CONSULTANT_HERITAGE, CONSULTANT_LIGHTING, CONSULTANT_PLANNING, CONSULTANT_SURVEY, CONSULTANT_SUSTAINABILITY, CONSULTANT_TECHNOLOGY, CONSULTANT_TRAFFIC. New disciplines may be added, so ignore a value you don’t recognise rather than failing. |
| Field | Type | Notes |
|---|---|---|
nextCursor | string | null | Pass as the cursor query parameter to fetch the next page. null when there are no more pages. |
hasNextPage | boolean | true when another page is available. Equivalent to nextCursor !== null. |
Pagination
Section titled “Pagination”Results are paged with an opaque cursor. Without createdSince they’re ordered by each project’s earliest tender quotesDueAt (ascending, undated projects last). With createdSince they’re ordered by createdAt (oldest first), then projectId. To walk every page:
- Make the first request with no
cursor(optionally setlimit). - If
_metadata.hasNextPageistrue, repeat the request withcursorset to_metadata.nextCursor, keeping the samelimitandcreatedSince. - Stop when
hasNextPageisfalse(nextCursorisnull).
The cursor encodes a position in the ordering, so pages never overlap or skip entries even as limit varies between requests. A cursor is only valid with the createdSince it was issued for: change or drop createdSince mid-walk and the request returns 400 INVALID_CURSOR, so start again from the first page. Treat the cursor as opaque — its contents are not part of the contract. Re-requesting the same cursor/limit within a short window is served from cache, so a paged sweep stays cheap and stable.
Matching semantics
Section titled “Matching semantics”- Results only include tenders that are currently open — quotes are not past due and fall within the near-term window. The window is server-side; partners do not control it.
- Matching is driven by the supplier account’s Speci-finder keywords (account-wide; resolved via the assigned user on the credential). If the account has no keywords configured, the response will be
200 OKwithdata: []. - See Data freshness for how quickly new data propagates.
Example request
Section titled “Example request”First page (default limit):
curl -X GET \ "https://api.estimateone.com/api/tender/supplier/v1/companies/{companyId}/speci-finder/results?limit=25" \ -H "Authorization: Bearer $TOKEN" \ -H "x-e1-client-id: $CLIENT_ID"Projects added since a given instant, oldest first:
curl -X GET \ "https://api.estimateone.com/api/tender/supplier/v1/companies/{companyId}/speci-finder/results?createdSince=2026-05-01T00:00:00Z&limit=100" \ -H "Authorization: Bearer $TOKEN" \ -H "x-e1-client-id: $CLIENT_ID"Next page — pass the previous response’s _metadata.nextCursor:
curl -X GET \ "https://api.estimateone.com/api/tender/supplier/v1/companies/{companyId}/speci-finder/results?limit=25&cursor=MjAyNi0wNS0xNXwxMjM0NQ" \ -H "Authorization: Bearer $TOKEN" \ -H "x-e1-client-id: $CLIENT_ID"Errors
Section titled “Errors”See Errors for the full catalogue. Common failures for this endpoint:
| Status | code | Cause |
|---|---|---|
| 400 | CLIENT_ID_HEADER_MISSING | x-e1-client-id header not sent. |
| 400 | INVALID_CURSOR | The cursor was tampered with, or was issued for a different company or createdSince. |
| 401 | CLIENT_ID_HEADER_MISMATCH | x-e1-client-id header does not match the token. |
| 401 | Unauthorised | Token invalid, company not registered, permission not published, or scope missing. |
| 5xx | FETCH_FAILED / INTERNAL_SERVER_ERROR | Something failed downstream. Retry with backoff. |
OpenAPI spec
Section titled “OpenAPI spec”The full OpenAPI 3.1 spec is published at https://api.estimateone.com/swagger/doc. Use it to drive client code generation in the language of your choice. You can also browse it interactively.