Contract document
The contract PDF is uploaded in two steps:
- Get the upload URL from the API.
- Upload the file straight to that URL.
Document rules:
- Upload a PDF without a password, as the automated reading requires.
- With
signature_method: external, upload the contract already signed. - While the contract is in
pending_submission, a new upload replaces the previous one. - On submission for review, the document is frozen and can no longer be replaced.
Get upload URL
Available on
| Profile | Host | Required permission |
|---|---|---|
| Manager | manager-api | Write |
| Consultant | consultant-api | Create Expense Contracts |
Base URL for each host: Environments (Hosts).
ENDPOINT
/expense_contract/fund_class/{fund_class_key}/contract/{contract_key}/document/upload_urlMETHOD
GETPath parameters
| Parameter | Type | Description |
|---|---|---|
fund_class_key | string | Fund class key (UUID). |
contract_key | string | Contract key (UUID). |
Response
STATUS
200The contract, in the structure described in contract attributes, with the additional field upload_url.
Response excerpt
{
"contract_key": "3f2b8c1e-7d4a-4e9b-9a61-0c5d2e8f4b17",
"status": "pending_submission",
"upload_url": {
"url": "https://<link-temporario-de-envio>",
"fields": {
"key": "3f2b8c1e-7d4a-4e9b-9a61-0c5d2e8f4b17_draft",
"x-amz-algorithm": "AWS4-HMAC-SHA256",
"x-amz-credential": "<credential>",
"x-amz-date": "20261109T141000Z",
"x-amz-security-token": "<token>",
"policy": "<policy>",
"x-amz-signature": "<signature>"
}
}
}
| Field | Type | Description |
|---|---|---|
upload_url.url | string | Address the file must be sent to. |
upload_url.fields | object | Fields that go along with the file. Send all of them, without changing names or values. Do not hard-code the list of fields: the exact set may vary. |
Upload the file
Make a POST multipart/form-data to upload_url.url:
- Include all the fields from
upload_url.fields. - Put the file in the
filefield, the last one in the form. - Do not send the QI Tech authentication headers: this call goes straight to storage.
The URL expires in 3 minutes and accepts files up to 50 MB.
import requests
upload = response_body["upload_url"]
with open("contrato_auditoria_2027.pdf", "rb") as arquivo:
resposta = requests.post(
upload["url"],
data=upload["fields"],
files={"file": arquivo},
)
resposta.raise_for_status()
A 204 response means the upload is complete. There is no confirmation call: the API only checks the document on submission for review. If the URL expires, request another one.
To check the uploaded file, download it through download_url in the contract retrieval.
Errors
| Status | code | When it happens |
|---|---|---|
| 404 | ECT000005 | The fund class does not exist or you do not have access to it. |
| 404 | ECT000009 | The contract does not exist in this fund class. |
| 409 | ECT000017 | The contract is not in pending_submission: the document has already been frozen and cannot be replaced. |
| 409 | ECT000046 | The contract was created by QI Tech during fund structuring and does not accept this call. |
Authentication, permission and host errors: see API Errors.
Next step
Submission for review: submit the contract for QI Tech review.