Skip to content

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}/packages
POST /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/packages
GET /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/download

Reads need packages/read; create, update and delete need packages/write. Downloading a package’s documents needs documents/read.

FieldTypeNotes
idstringNumeric string.
namestring
tradeDisciplineIdstring | nullReferences a trade discipline.
quotesDueDatestring | nullYYYY-MM-DD. Falls back to the project’s date when not set.
Terminal window
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.

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.

DELETE returns 204 No Content with an empty body.

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:

Terminal window
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.

StatusWhen
400The package ID or redirect isn’t valid.
401The token doesn’t carry documents/read.
403The builder restricts the project to certain team members.
404The project or package doesn’t exist, belongs to another company, or the package has no documents.
500E1 couldn’t create the download link. Try again shortly.

tradeDisciplineId must come from the company’s trade discipline list:

GET /api/tender/builder/v1/companies/{companyId}/trade-disciplines

This 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.