InsertExpense
Expenses service 1.0: Creates one expense for an employee on a project and returns it.
| Service | Expenses 1.0 |
| Endpoint | POST https://app{server-number}.timelog.com/{account}/WebServices/Expenses/V1_0/ExpensesServiceSecure.svc |
| SOAPAction | http://www.timelog.com/api/tlp/v1_0/ExpenseService/InsertExpense |
| Content-Type | text/xml; charset=utf-8 |
| Authentication | A token from GetToken |
| Permissions | Expenses of other employees: privilege 'all registrations'. |
| Changes data | Yes |
| Safe to retry | Only with an ExternalID: a repeat in the same project legal entity stores nothing. Without ExternalID every call creates a new expense. |
How it works
Registers an expense on the project given by ProjectNo, for the employee given by EmployeeInitials. The expense goes on the project's main contract. Required: ProjectNo, CurrencyISO, ExpenseTypeID, ProductNo, the employee, PurchaseDate and the amounts. The server computes cost, sales amount and reimbursement in the system, project and legal-entity currencies; the expense number comes from auto-numbering when it is active. On failure ResponseState is Error, code 40001, the reason is in the message text and Return[0] may still hold the expense you sent: check ResponseState. Retry safety: set ExternalID. A second insert with an ExternalID already used in the project's legal entity stores nothing - but also reports Success, so confirm with GetExpensesByEmployee.
Parameters
Each parameter has one label: Required, Conditional (required depending on other values) or Optional. Send them in the order shown.
The Expense to send. Its fields are listed below.
The token from GetToken: Initials, Expires and Hash, sent back unchanged.
Request
POST /{account}/WebServices/Expenses/V1_0/ExpensesServiceSecure.svc HTTP/1.1
Host: app{server-number}.timelog.com
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://www.timelog.com/api/tlp/v1_0/ExpenseService/InsertExpense"
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:expenses="http://www.timelog.com/api/tlp/v1_0"
xmlns:tok="http://www.timelog.com/api/tlp/v1_3"
xmlns:expense="http://api.timelog.com/services/expense/1_0">
<soapenv:Body>
<expenses:InsertExpense>
<expenses:expense>
<expense:CurrencyISO>{CurrencyISO}</expense:CurrencyISO>
<expense:EmployeeInitials>{EmployeeInitials}</expense:EmployeeInitials>
<expense:ExpenseTypeID>{ExpenseTypeID}</expense:ExpenseTypeID>
<expense:ProductNo>{ProductNo}</expense:ProductNo>
<expense:ProjectNo>{ProjectNo}</expense:ProjectNo>
<expense:PurchaseDate>{PurchaseDate}</expense:PurchaseDate>
<expense:PurchasedAmountInCurrency>{PurchasedAmountInCurrency}</expense:PurchasedAmountInCurrency>
<expense:EmployeeNo>{EmployeeNo}</expense:EmployeeNo>
<!-- more fields: see Parameters -->
</expenses:expense>
<expenses:token>
<tok:Initials>{Initials from GetToken}</tok:Initials>
<tok:Expires>{Expires from GetToken, unchanged}</tok:Expires>
<tok:Hash>{Hash from GetToken}</tok:Hash>
</expenses:token>
</expenses:InsertExpense>
</soapenv:Body>
</soapenv:Envelope>
curl -X POST "https://app{server-number}.timelog.com/{account}/WebServices/Expenses/V1_0/ExpensesServiceSecure.svc" \
-H "Content-Type: text/xml; charset=utf-8" \
-H 'SOAPAction: "http://www.timelog.com/api/tlp/v1_0/ExpenseService/InsertExpense"' \
--data-binary @request.xml
import requests
URL = "https://app{server-number}.timelog.com/{account}/WebServices/Expenses/V1_0/ExpensesServiceSecure.svc"
SOAP_ACTION = '"http://www.timelog.com/api/tlp/v1_0/ExpenseService/InsertExpense"'
envelope = """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:expenses="http://www.timelog.com/api/tlp/v1_0"
xmlns:tok="http://www.timelog.com/api/tlp/v1_3"
xmlns:expense="http://api.timelog.com/services/expense/1_0">
<soapenv:Body>
<expenses:InsertExpense>
<expenses:expense>
<expense:CurrencyISO>{CurrencyISO}</expense:CurrencyISO>
<expense:EmployeeInitials>{EmployeeInitials}</expense:EmployeeInitials>
<expense:ExpenseTypeID>{ExpenseTypeID}</expense:ExpenseTypeID>
<expense:ProductNo>{ProductNo}</expense:ProductNo>
<expense:ProjectNo>{ProjectNo}</expense:ProjectNo>
<expense:PurchaseDate>{PurchaseDate}</expense:PurchaseDate>
<expense:PurchasedAmountInCurrency>{PurchasedAmountInCurrency}</expense:PurchasedAmountInCurrency>
<expense:EmployeeNo>{EmployeeNo}</expense:EmployeeNo>
<!-- more fields: see Parameters -->
</expenses:expense>
<expenses:token>
<tok:Initials>{Initials from GetToken}</tok:Initials>
<tok:Expires>{Expires from GetToken, unchanged}</tok:Expires>
<tok:Hash>{Hash from GetToken}</tok:Hash>
</expenses:token>
</expenses:InsertExpense>
</soapenv:Body>
</soapenv:Envelope>"""
response = requests.post(
URL,
data=envelope.encode("utf-8"),
headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": SOAP_ACTION},
timeout=60,
)
result = parse_result(response) # see "Responses and errors"
{…} placeholder with your own value.Response
The call returns the standard response envelope. Check ResponseState first: a failed call still returns HTTP 200.
Success, Invalid or Error.
0 on success, otherwise the error code. See Errors.
Response<Expense>: Return[0] = the expense as sent plus its GUID and auto-number; ImageUrlList empty.
Example response
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<InsertExpenseResponse xmlns="http://www.timelog.com/api/tlp/v1_0">
<InsertExpenseResult xmlns:a="http://api.timelog.com"
xmlns:b="http://api.timelog.com/services/expense/1_0"
xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Return>
<b:Expense>
<b:ID>3f2504e0-4f89-41d3-9a0c-305000000001</b:ID>
<b:Comment>Example</b:Comment>
<b:CurrencyISO>DKK</b:CurrencyISO>
<b:CurrencyRate>0</b:CurrencyRate>
<b:ExpenseNo>1001</b:ExpenseNo>
<b:EmployeeInitials>Example</b:EmployeeInitials>
<b:ExpenseTypeID>3f2504e0-4f89-41d3-9a0c-305000000002</b:ExpenseTypeID>
<b:ExternalID>Example</b:ExternalID>
<b:InvoiceNo>1001</b:InvoiceNo>
<b:PaymentMethodID>1001</b:PaymentMethodID>
<b:ProductNo>1001</b:ProductNo>
<b:ProfitPercent>0</b:ProfitPercent>
<b:ProjectNo>1001</b:ProjectNo>
<b:PurchaseDate>2026-10-01T00:00:00</b:PurchaseDate>
<b:PurchasedAmountInCurrency>0</b:PurchasedAmountInCurrency>
<b:NonBillable>false</b:NonBillable>
<b:VATAmount>0</b:VATAmount>
<b:EmployeeNo>1001</b:EmployeeNo>
<b:Category>Employee</b:Category>
<b:ImageUrlList>
<c:anyURI>Example</c:anyURI>
</b:ImageUrlList>
</b:Expense>
</a:Return>
<a:ReturnTypeDescription>Expense</a:ReturnTypeDescription>
<a:Request>…</a:Request>
<a:ResponseState>Success</a:ResponseState>
<a:Messages>
<a:APIMessage>
<a:Message>Duration of call was 0,0421337 sec.</a:Message>
<a:Severity>Message</a:Severity>
<a:ErrorCode>0</a:ErrorCode>
</a:APIMessage>
</a:Messages>
<a:ErrorCode>0</a:ErrorCode>
<a:IsReturnPaged>false</a:IsReturnPaged>
<a:TotalPageCount>0</a:TotalPageCount>
</InsertExpenseResult>
</InsertExpenseResponse>
</s:Body>
</s:Envelope>
Request block is shortened.Errors
All errors come back with HTTP 200: check ResponseState, ErrorCode and Messages. The errors every operation can return (expired or invalid token, API not enabled, HTTPS required, general errors) are listed once in Responses and errors.
Errors specific to this operation:
| ResponseState | ErrorCode | Message | What it means | What to do |
|---|---|---|---|---|
| Error | 40001 | Error during call on TimeLog.API, Message 'The current user do not have the right privileges to add expenses for other users' | EmployeeInitials not the caller's and no 'all registrations' privilege. | Register only your own expenses, or use an integration user with the 'all registrations' privilege. |
| Error | 40001 | Error during call on TimeLog.API, Message 'ProductNo is mandatory and is missing' | The value is required although it is never stored. | — |
| Error | 40001 | Error during call on TimeLog.API, Message 'Currency ISO is empty or not valid' | Expense.CurrencyISO is empty or not a currency abbreviation known to the account. | — |
| Error | 40001 | Error during call on TimeLog.API, Message 'Either employee initials or no is needed. Employee not found.' | Neither EmployeeInitials nor EmployeeNo identifies an employee. | Send the initials of an existing employee. |
| Error | 40001 | Error during call on TimeLog.API, Message 'Expense type is not valid' | Expense.ExpenseTypeID is not the GUID of an expense type. | Use an ID returned by GetExpenseTypes. |
| Error | 40001 | Error during call on TimeLog.API, Message 'Project no is missing or project is not found.' | Expense.ProjectNo is missing or no project has that number. | Send the number of an existing project. |
| Error | 40001 | Error during call on TimeLog.API, Message '{business-rule message}' | ExternalID ''; project's main contract inactive / prepaid / fixed price with NonBillable; expense type required by the account. | Read the embedded message and fix the expense or the project's contract. |
| Error | 40001 | Error during call on TimeLog.API, Message 'Insert of disbursement failed because there was no payment method set as default for import of expenses!' | InsertExpense with PaymentMethodID 0 on a account without a default payment method for expense import. | Send PaymentMethodID from GetPaymentMethods, or configure a default import payment method. |
| Error | 40001 | Error during call on TimeLog.API, Message 'Insert of disbursement failed!' | — | — |