ExpensesAttach PDF files to a project expense

Attach PDF files to a project expense

Uploads one or more PDF files (e.g. the supplier invoice) to an existing project expense.

The supplier-invoice write path of accounts-payable integrations. Send multipart/form-data with one part per PDF; every part is treated as a file (no form fields). Each part must carry a PDF signature, else the whole call fails and nothing is stored. The expense must already exist: create it first, then attach.

Use it to:

  • BPP / accounts-payable integration attaching the supplier-invoice PDF to the project cost line it booked

Not for:

  • Images — POST /v1/project-expense/{expenseID}/image
  • Replacing a file — delete it (deletefile) and attach the new one; attach only appends

Behaviour:

  • Not idempotent.
  • Not safe to retry blindly. After a lost response, GET the expense and compare FileUrls before retrying.
  • Duplicates: Not deduplicated: the same PDF uploaded twice becomes two attachments.
  • On failure: All parts are checked before any upload, so an invalid part stores nothing now — but earlier valid parts stay queued on the expense object and are uploaded by the next attach.
  • Changes are visible to the next read immediately.
  • Batch: For validation of the current call only. Not transactional for storage failures.

Call first: POST /v1/project-expense

Next: GET /v1/project-expense/{expenseID}

See also: POST /v1/project-expense/{expenseID}/image, DELETE /v1/project-expense/{expenseID}/file, POST /v1/employee-expense/{employeeExpenseID}/file

Not yet documented: examples.

curl -X POST "https://app3.timelog.com/your-account/api/v1/project-expense/123/file" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
"null"
POST
/v1/project-expense/{expenseID}/file
POST
Base URLstring

Target server for requests. Edit to use your own host.

Bearer Token
Bearer Tokenstring
Required

OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp). The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user. > Not yet documented: how to obtain a token. > Not yet documented: token lifetime.

OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp). The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user. > Not yet documented: how to obtain a token. > Not yet documented: token lifetime.
path
expenseIDinteger
Required

The project expense to attach to. An employee-expense ID is rejected (500 Code 100).

Format: int32
Request Preview
Response

Response will appear here after sending the request

Authentication

header
Authorizationstring
Required

Bearer token. OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).

The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.

Not yet documented: how to obtain a token.

Not yet documented: token lifetime.

Path Parameters

expenseIDinteger
Required

The project expense to attach to. An employee-expense ID is rejected (500 Code 100).

Responses

*Success without content: HTTP 200 with the literal JSON body null. Body is the JSON literal null. The new files appear in FileUrls of GET /v1/project-expense/{id}.

Order: none

Empty result: none*