Quotes
A quote is what a subcontractor sent in response to an invitation, or one you uploaded yourself. Each time a subcontractor revises their quote, the new revision gets its own ID and the old one is kept.
GET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/quotesGET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/quotes/{quoteId}GET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/quotes/{quoteId}/downloadAll need quotes/read, and quotes must be enabled for your company in E1.
The quote object
Section titled “The quote object”| Field | Type | Notes |
|---|---|---|
id | string | Numeric string. Each revision has its own ID. |
invitationId | string | null | The invitation the quote was received against; null for a quote you uploaded yourself. |
packageId | string | null | null for an uncategorised quote, which isn’t assigned to a package yet. |
packageName | string | null | |
version | integer | 1 for the original quote, incremented by each revision. |
isLatestRevision | boolean | false when a newer revision has been received. |
amountCents | integer | null | The quoted amount in cents, excluding tax (GST/VAT). null if the quote has no amount. |
submittedAt | string | null | ISO 8601 timestamp. When this revision was received. |
invitee | object | null | Who sent it, in the same shape as the invitation’s invitee. null when it wasn’t received against an invitation. |
files | object[] | The quote’s files. See below. |
viewed | boolean | Whether anyone in your company has downloaded the quote. |
lastDownloadedAt | string | null | When someone in your company last downloaded it. |
| Field | Type | Notes |
|---|---|---|
id | string | primary for the quote document itself, otherwise the additional file’s ID. |
name | string | The file name. |
size | integer | null | Size in bytes. |
primary | boolean | Whether this is the quote document rather than an additional file. |
Only file metadata is returned. To get the files, download the quote.
Listing quotes
Section titled “Listing quotes”GET .../quotes returns the latest revision of each quote on the project,
oldest first. Deleted quotes are not included.
| Query parameter | Notes |
|---|---|
packageId | Optional. Only list quotes for this package. |
uncategorised | Optional. true to only list quotes not yet assigned to a package. Can’t be combined with packageId. |
limit | Optional. Page size, 1 to 100. Defaults to 25. |
cursor | Optional. The _metadata.nextCursor from the previous page. |
The list is paginated. While _metadata.hasNextPage is true, request the next
page with cursor set to _metadata.nextCursor and the same filter. The cursor
is signed and tied to the project and filter it came from: pass it back
unchanged. A cursor that was modified, or that came from a different listing,
returns 400.
curl -sS \ -H "Authorization: Bearer $E1_ACCESS_TOKEN" \ -H "x-e1-client-id: $E1_CLIENT_ID" \ "$E1_API_BASE/api/tender/builder/v1/companies/$E1_COMPANY_ID/tender-projects/198578/quotes?packageId=1930318"{ "data": [ { "id": "9001", "invitationId": "7001", "packageId": "1930318", "packageName": "Joinery", "version": 2, "isLatestRevision": true, "amountCents": 12500000, "submittedAt": "2026-09-05T12:00:00+10:00", "invitee": { "addressBookCompanyId": "3001", "addressBookContactId": "4001", "companyName": "Acme Joinery", "contactName": "Jo Smith" }, "files": [ { "id": "primary", "name": "Acme Joinery quote.pdf", "size": 482133, "primary": true }, { "id": "6001", "name": "Schedule of rates.xlsx", "size": 20480, "primary": false } ], "viewed": true, "lastDownloadedAt": "2026-09-06T08:00:00+10:00" } ], "_metadata": { "nextCursor": null, "hasNextPage": false }}Reading a quote
Section titled “Reading a quote”GET .../quotes/{quoteId} returns one quote as data, without _metadata. It
works for older revisions too. As well as the fields above, it includes:
| Field | Type | Notes |
|---|---|---|
notes | object[] | Your team’s notes on the quote, oldest first: id, text, authorName and createdAt. Mentions of colleagues appear in text as @[Name](userId). |
revisions | object[] | Every revision of the quote, oldest first, including this one: id, version, amountCents and submittedAt. Read any of them by its id. |
{ "data": { "id": "9001", "version": 2, "isLatestRevision": true, "amountCents": 12500000, "notes": [ { "id": "5101", "text": "Cheapest so far, includes pump hire.", "authorName": "Pat Estimator", "createdAt": "2026-09-06T09:00:00+10:00" } ], "revisions": [ { "id": "9000", "version": 1, "amountCents": 13000000, "submittedAt": "2026-09-04T12:00:00+10:00" }, { "id": "9001", "version": 2, "amountCents": 12500000, "submittedAt": "2026-09-05T12:00:00+10:00" } ] }}The other quote fields are omitted from this example.
Downloading a quote
Section titled “Downloading a quote”GET .../quotes/{quoteId}/download gets every file on the quote in one
download. A quote with only its quote document downloads as that file. A quote
with additional files downloads as a .zip holding all of them.
The files are not returned in the response body. Instead the API responds
302 Found, and the Location header holds a short-lived URL that serves the
download. Most HTTP clients follow the redirect for you:
curl -sS -L -OJ \ -H "Authorization: Bearer $E1_ACCESS_TOKEN" \ -H "x-e1-client-id: $E1_CLIENT_ID" \ "$E1_API_BASE/api/tender/builder/v1/companies/$E1_COMPANY_ID/tender-projects/198578/quotes/9001/download"If your client can’t follow redirects, or you want to hand the URL to something
else, add ?redirect=false to get it as JSON instead:
{ "data": { "url": "https://fm.prod.filemanager.estimateone.com/download?token=...", "expiresAt": "2026-10-06T01:05:00+00:00", "name": "Acme_Joinery_quote_pdf.zip" }}name is the file name for a single-file quote, otherwise the name of the
.zip.
The URL is valid for 5 minutes and needs no API credentials, so anyone holding
it can download the quote until expiresAt. Don’t log or store it; request a
new one each time you need the files. The redirect goes to a different host
from the API, and your client should not forward your Authorization header to
it.
Each request counts as your company downloading the quote, so it marks the
quote as viewed and updates lastDownloadedAt, the same as downloading it in
E1.
Errors
Section titled “Errors”| Status | When |
|---|---|
400 | limit, packageId, uncategorised or the quote ID isn’t valid, packageId was combined with uncategorised=true, or the cursor was modified or came from a different listing (INVALID_CURSOR). |
401 | The token doesn’t carry quotes/read. |
403 | Quotes aren’t enabled for your company, or the builder restricts the project to certain team members. |
404 | The project, package or quote doesn’t exist, or belongs to another company. |