Skip to content

Documents

Documents are the drawings and specifications issued on a project. This page covers reading, downloading and managing documents that already exist; getting a new file onto a project is a separate flow, described in Upload jobs.

GET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/documents
GET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/documents/{documentId}
GET /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/documents/{documentId}/download
PUT /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/documents/{documentId}
DELETE /api/tender/builder/v1/companies/{companyId}/tender-projects/{projectId}/documents/{documentId}

Reads and downloads need documents/read; update and delete need documents/write.

FieldTypeNotes
idstringNumeric string.
namestringFile name as issued, including extension.
directorystringFolder path within the project, e.g. /Architectural. / is the root.
titlestring | nullHuman-readable title, independent of the file name.
drawingIdstring | nullThe builder’s drawing number.
revisionstring | nullRevision label, e.g. C, 2.
sizenumberSize in bytes.
addendumIdstring | nullThe addendum the document was added or revised in; null for the original issue.
addendumStatusstringconfirmed once issued; new or revised while it is in the pending addendum.
supersedesDocumentIdstring | nullThe document this one revises, or will revise once its addendum is issued.

GET .../documents returns every current document on the project. GET .../documents/{documentId} returns one.

Terminal window
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/documents"
{
"data": [
{
"id": "8074957",
"name": "A-101 Ground Floor Plan.pdf",
"directory": "/Architectural",
"title": "Ground Floor Plan",
"drawingId": "A-101",
"revision": "C",
"size": 482113,
"addendumId": null,
"addendumStatus": "confirmed",
"supersedesDocumentId": null
}
]
}

GET .../documents/{documentId}/download gets the file itself. The file is not returned in the response body. Instead the API responds 302 Found, and the Location header holds a short-lived URL that serves the file. Most HTTP clients follow the redirect for you:

Terminal window
curl -sS -L -o "A-101 Ground Floor Plan.pdf" \
-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/documents/8074957/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.000Z",
"name": "A-101 Ground Floor Plan.pdf",
"contentType": "application/pdf",
"size": 482113
}
}

The URL is valid for 5 minutes and needs no API credentials, so anyone holding it can download the file until expiresAt. Don’t log or store it; request a new one each time you need the file. The redirect goes to a different host from the API, and your client should not forward your Authorization header to it.

To get every document in a package at once, download the package’s documents as a .zip.

PUT .../documents/{documentId} changes a document’s metadata. It does not replace the file — to issue a new revision of the file itself, run an upload job against the document.

The body carries every field except id, size and the addendum fields, which are all derived:

Terminal window
curl -sS -X PUT \
-H "Authorization: Bearer $E1_ACCESS_TOKEN" \
-H "x-e1-client-id: $E1_CLIENT_ID" \
-H "content-type: application/json" \
-d '{
"data": {
"name": "A-101 Ground Floor Plan.pdf",
"directory": "/Architectural",
"title": "Ground Floor Plan (revised)",
"drawingId": "A-101",
"revision": "D"
}
}' \
"$E1_API_BASE/api/tender/builder/v1/companies/$E1_COMPANY_ID/tender-projects/198578/documents/8074957"

Returns 200 OK with the updated document. Like the project update, this is a full replacement: omitted nullable fields are read as null.

If the document has a pending revision in the project’s pending addendum, the change is made to that revision and the response carries the revision.

DELETE .../documents/{documentId} returns 204 No Content.

While the project has a pending addendum, only documents added or revised in it can be deleted. Deleting a revision leaves the document it was revising as it was. Deleting any other document is refused, because the deletion would take effect immediately rather than being issued with the addendum.

A pending addendum can refuse a write with 400 Bad Request, code UPDATE_FAILED. detail says why:

SituationApplies to
The document isn’t part of the pending addendum.DELETE
The document already has a pending revision. Change or delete the revision instead.PUT, DELETE
The project has unconfirmed package changes.DELETE
The pending addendum is being issued. Retry shortly.PUT, DELETE

Two project states change what the write endpoints do:

StateBehaviour
Archived projectEvery request for the project returns 404 Not Found, as though it did not exist.
Connected projectReads work normally. PUT and DELETE on documents, and PUT on the project, return 403 Forbidden with code FORBIDDEN.

A connected project is one whose documents are maintained through a linked system rather than directly on E1, so E1 is not the source of truth for them.