ExpensesAttach images to a project expense

Attach images to a project expense

Uploads one or more images (JPEG, PNG, GIF, BMP, TIFF …) to an existing project expense, e.g. a scanned supplier invoice.

Send multipart/form-data with one part per image; every part is treated as a file (no form fields). Each part must decode as an image, else the whole call fails and nothing is stored. WARNING: unlike employee expenses, a project expense never lists its images — GET returns PDFs only in FileUrls — so an image attached here cannot be seen or deleted through the API afterwards. For supplier invoices, prefer the PDF endpoint. The expense must already exist: create it first, then attach.

Use it to:

  • Accounts-payable / BPP integration attaching a scanned supplier invoice to the cost line it booked

Not for:

  • PDF supplier invoices — POST /v1/project-expense/{expenseID}/file (and the only attachments GET returns)
  • Employee receipts — POST /v1/employee-expense/{employeeExpenseID}/image
  • Replacing an attachment — attach only appends

Behaviour:

  • Not idempotent.
  • Not safe to retry blindly. The API cannot show whether a lost call succeeded.
  • Duplicates: Not deduplicated: the same image uploaded twice becomes two attachments.
  • 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

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

Not yet documented: examples; request → body → multipart → formats.

curl -X POST "https://app3.timelog.com/your-account/api/v1/project-expense/123/image" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
"null"
POST
/v1/project-expense/{expenseID}/image
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): the loader filters on the project category.

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): the loader filters on the project category.

Responses

*Success without content: HTTP 200 with the literal JSON body null. Body is the JSON literal null. The image is NOT visible afterwards in GET /v1/project-expense/{id}.

Order: none

Empty result: none*