Packages
A package is a parcel of work within a project — the unit subcontractors quote against. Packages hang off a project.
GET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/packagesPOST /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/packagesGET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/packages/{packageId}PUT /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/packages/{packageId}DELETE /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/packages/{packageId}GET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/packages/{packageId}/documents/downloadReads need packages/read; create, update and delete need packages/write.
Downloading a package’s documents needs
documents/read.
The package object
Section titled “The package object”| Field | Type | Notes |
|---|---|---|
id | string | Numeric string. |
name | string | |
tradeDisciplineId | string | null | References a trade discipline. |
quotesDueDate | string | null | YYYY-MM-DD. Falls back to the project’s date when not set. |
Creating a package
Section titled “Creating a package”curl -sS -X POST \ -H "Authorization: Bearer $E1_ACCESS_TOKEN" \ -H "x-e1-client-id: $E1_CLIENT_ID" \ -H "content-type: application/json" \ -d '{ "data": { "name": "Bulk Earthworks", "tradeDisciplineId": "48604", "quotesDueDate": "2026-05-15" } }' \ "$E1_API_BASE/api/tender/builder/v1/companies/$E1_COMPANY_ID/tender-projects/198580/packages"Returns 201 Created with the new package. All three fields are required in the
body, but tradeDisciplineId and quotesDueDate accept null.
Updating a package
Section titled “Updating a package”PUT takes the same body as POST and replaces all three fields, so send the
values you want to keep as well as the ones you are changing. Returns the
updated package.
Deleting a package
Section titled “Deleting a package”DELETE returns 204 No Content with an empty body.
Downloading a package’s documents
Section titled “Downloading a package’s documents”GET .../packages/{packageId}/documents/download gets every document in the
package as one .zip, even when the package holds a single document. The zip
keeps each document’s folder, and is the same one builders download from E1.
The zip is not returned in the response body. Instead the API responds
302 Found, and the Location header holds a short-lived URL that serves it.
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/packages/1930318/documents/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": "example-project-joinery.zip" }}The URL is valid for 5 minutes and needs no API credentials, so anyone holding
it can download the documents until expiresAt. Don’t log or store it; request
a new one each time you need the documents. The redirect goes to a different
host from the API, and your client should not forward your Authorization
header to it.
A package with no documents returns 404. To download a single document
instead, see Downloading the file.
| Status | When |
|---|---|
400 | The package ID or redirect isn’t valid. |
401 | The token doesn’t carry documents/read. |
403 | The builder restricts the project to certain team members. |
404 | The project or package doesn’t exist, belongs to another company, or the package has no documents. |
500 | E1 couldn’t create the download link. Try again shortly. |
Trade disciplines
Section titled “Trade disciplines”tradeDisciplineId must come from the company’s trade discipline list:
GET /api/tender/builder/v1/companies/{companyId}/trade-disciplinesThis needs the trade-disciplines/read scope, which packages/read already
implies. The list is stable enough to cache for the length of a sync run, but
treat it as per-company rather than global.