Create a mileage registration
Registers a car trip (mileage) on a project for the caller or on behalf of another user, optionally with its travel time.
Creates a mileage registration that is reimbursed to the employee and, when billable, invoiced to the customer at the mileage rate. Distance is one-way whole kilometres; IsRoundtrip doubles it. The contract is the one you send or the project's default mileage contract. With TravelTimeRegistration the same call also creates a TravelTime time registration on a travel-time task of the project and links it. Returns the created registration (same shape as GET /v1/mileage-registration/{id}).
Use it to:
- Fleet / travel integration registering trips for employees (UserID, on-behalf privileges)
Not for:
- Travel time without a trip — create a time registration (GroupType TravelTime)
- Out-of-pocket costs —
POST /v1/employee-expense
Rules across fields:
- R1: ProjectSubContractID, when > 0, must belong to ProjectID (and be neither prepaid-services nor closed).
- R2: When ShowOdometer is on and either odometer is sent, OdometerEnd > OdometerStart (400). With OdometerIsRequired also on and both sent, Distance × (IsRoundtrip ? 2 : 1) must equal OdometerEnd − OdometerStart.
- R3: A personal CarID must belong to the owner (UserID or caller).
- R4: MileageRateID must have a rate period covering Date.
- R5: TravelTimeRegistration.TaskID must be a task of ProjectID.
- R6: With TravelTimeRegistration, a body TimeRegistrationID is overwritten by the new travel-time registration's ID.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. Every call creates a new registration (new GUID); nothing deduplicates. After a lost response, list your registrations (or the owner's, via GET by ID) before retrying.
- Duplicates: Not deduplicated (except one mileage registration per TimeRegistrationID, 37825).
- Side effect: Creates the registration with expense number (auto-number), travel product number and system text "<Mileage> (<owner name>) <start> » <end> <TotalDistance> km.".
- Side effect: Creates mileage financial data and invoicing potential, recalculates the contract's estimated values, creates the employee reimbursement posting.
- Side effect: With TravelTimeRegistration: creates a TravelTime time registration (owner, Date, Purpose as comment, billable hours = Hours when the task is billable) and links it.
- On failure: With TravelTimeRegistration: the time registration is created first and deleted again if the mileage save throws (compensation, not one transaction).
- Changes are visible to the next read immediately.
- The mileage insert and its side effects run in one transaction scope; the response is read back after commit.
Call first: GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration, GET /v1/task/travel-time-task-for-time-tracking
Next: GET /v1/mileage-registration/{mileageRegistrationID}, PUT /v1/mileage-registration
See also: POST /v1/project-header/mileage-registration
curl -X POST "https://app3.timelog.com/your-account/api/v1/mileage-registration" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/mileage-registration"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/mileage-registration", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/mileage-registration", bytes.NewBuffer(data))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
require 'net/http'
require 'json'
uri = URI('https://app3.timelog.com/your-account/api/v1/mileage-registration')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request.body = '{
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}'
response = http.request(request)
puts response.body
{
"Properties": {
"MileageRegistrationID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"UnitType": 42,
"TotalDistance": 3.14,
"IsRoundtrip": true,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": 123,
"CarID": 123,
"MileageRatesID": 123,
"IsBillable": true,
"IsApproved": true,
"IsInvoiced": true,
"InterfaceCreated": 42,
"InterfaceModified": 42,
"Project": {
"ProjectID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"No": "example_string",
"ExpenseIsBillable": "null",
"CustomerID": 123,
"Customer": {
"CustomerID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"No": "example_string",
"DefaultMileageDistance": 25,
"CustomerStatusID": 123,
"VATNo": "example_string",
"OrganizationNo": "example_string"
},
"MainContractId": 123
},
"Car": {
"CarID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"LicensePlate": "example_string",
"CarType": 42,
"EmployeeUserID": "null",
"EmployeeUser": {
"UserID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"FirstName": "John Doe",
"LastName": "John Doe",
"Initials": "example_string",
"IsActive": true,
"UserType": "Employee",
"LegalEntityID": 123,
"Email": "user@example.com"
},
"IsActive": true,
"DefaultMileageRate": {
"ID": "123e4567-e89b-12d3-a456-426614174000",
"MileageRateID": 123,
"Name": "John Doe",
"IsActive": true,
"SalaryCodeTaxFree": "null",
"SalaryCodeTaxable": "null",
"AccountNumber": "null",
"IsDefaultForPrivateCar": true,
"HasReimbursement": true,
"UnitType": 42,
"ReimbursementCurrencyID": 123,
"LegalEntityID": 123
},
"IsDefault": true,
"AllowEmployeeChangeMileageRate": true
},
"MileageRate": {
"ID": "123e4567-e89b-12d3-a456-426614174000",
"MileageRateID": 123,
"Name": "John Doe",
"IsActive": true,
"SalaryCodeTaxFree": "null",
"SalaryCodeTaxable": "null",
"AccountNumber": "null",
"IsDefaultForPrivateCar": true,
"HasReimbursement": true,
"UnitType": 42,
"ReimbursementCurrencyID": 123,
"LegalEntityID": 123
},
"TravelTimeRegistration": {
"TimeRegistrationID": 123,
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42,
"Comment": "null"
}
},
"Links": [
{
"Href": "example_string",
"Rel": "example_string"
}
],
"Actions": [
{}
]
}
{
"Code": 200,
"Message": "Invalid model state error.",
"Details": [
"…"
],
"Parameters": "…",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/mileage-registration"
}
{
"Message": "Authorization has been denied for this request."
}
{
"Code": 103,
"Message": "…",
"Details": [],
"Parameters": "…",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/mileage-registration"
}
{
"Code": 100,
"Details": [],
"Message": "Unknown error occur in system, please contact system administrator.",
"Parameters": "model: null",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/mileage-registration"
}
/v1/mileage-registration
Target server for requests. Edit to use your own host.
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.
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.The media type of the request body
The date of the trip. Mapped as sent (the adapter does not truncate a time component, unlike the expense adapter). Must have a mileage-rate period covering it (37845) and must not be in a closed accounting period (37827).
Why the trip was made. Becomes part of the registration; not part of the generated system text. - empty or whitespace — Not checked by the 400 validator; the domain object raises 60107 (HTTP 500, Code 102).
Odometer reading at the start of the trip.
Odometer reading at the end of the trip. - both odometers sent, ShowOdometer and OdometerIsRequired on, and Distance (×2 if IsRoundtrip) ≠ OdometerEnd − OdometerStart — 60100 (HTTP 500, Code 102). Distance is never derived from the odometers.
One-way distance of the trip. The stored total distance is Distance, doubled when IsRoundtrip is true. Range: min 1.
Where the trip started (free text).
Where the trip ended (free text).
The project the trip is registered on.
Refers to ProjectHeaderRead.ProjectID. Look up valid values with GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
No check that the project is active. No check that the owner is allocated to it.
Valid values come from GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
The project contract the mileage is booked on.
Refers to ContractRead.ProjectSubContractID.
- omitted or 0 — The project's default-for-expenses contract, else its main contract; prepaid-services contracts are skipped. None → 37854.
- > 0 — Must be a contract of ProjectID (37042), not prepaid services (37847) and not closed (37846).
The user the mileage is registered for. Omit (or send your own UserID) to register for yourself.
Refers to UserRead.UserID.
Default: the caller
- ≤ 0 — 400, Code 200.
The car used. A personal car must belong to the registration's owner; a shared car (no employee) is accepted.
Refers to CarRead.CarID. Look up valid values with GET /v1/car.
Valid values come from GET /v1/car.
The mileage rate the distance is reimbursed and invoiced at. Must have a rate period covering Date.
Refers to MileageRateRead.MileageRateID. Look up valid values with GET /v1/mileage-rate.
true = the trip was there and back; the stored total distance is Distance × 2.
Optional link to a time registration (for example the travel time of the same trip).
Refers to TimeRegistrationRead.TimeRegistrationID.
- another mileage registration already links to it — 37825 (HTTP 500, Code 102).
No check that the time registration exists or belongs to the same owner or project.
Whether the mileage is billable to the customer. - account features Billable AND BillableExpense are on — applied - either feature is off — Replaced by the resolved contract's IsMileageBillable.
Optional travel time to register together with the mileage (POST /v1/mileage-registration only). - sent to POST /v1/mileage-registration — A TravelTime time registration is created first (owner, Date, Purpose as comment) and linked via TimeRegistrationID; it is deleted again if the mileage save fails. TimeRegistrationID in the body is then overwritten. - sent to POST /v1/project-header/mileage-registration — That route binds MileageRegistrationApiCreateModelForProject, which has no such property.
Request Preview
Response
Response will appear here after sending the request
Authentication
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.
Body
The date of the trip.
Mapped as sent (the adapter does not truncate a time component, unlike the expense adapter). Must have a mileage-rate period covering it (37845) and must not be in a closed accounting period (37827).
Why the trip was made. Becomes part of the registration; not part of the generated system text.
- empty or whitespace — Not checked by the 400 validator; the domain object raises 60107 (HTTP 500, Code 102).
Odometer reading at the start of the trip.
Odometer reading at the end of the trip.
- both odometers sent, ShowOdometer and OdometerIsRequired on, and Distance (×2 if IsRoundtrip) ≠ OdometerEnd − OdometerStart — 60100 (HTTP 500, Code 102). Distance is never derived from the odometers.
One-way distance of the trip. The stored total distance is Distance, doubled when IsRoundtrip is true.
Range: min 1.
Where the trip started (free text).
Where the trip ended (free text).
The project the trip is registered on.
Refers to ProjectHeaderRead.ProjectID. Look up valid values with GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
No check that the project is active. No check that the owner is allocated to it.
Valid values come from GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
The project contract the mileage is booked on.
Refers to ContractRead.ProjectSubContractID.
-
omitted or 0 — The project's default-for-expenses contract, else its main contract; prepaid-services contracts are skipped. None → 37854.
-
0 — Must be a contract of ProjectID (37042), not prepaid services (37847) and not closed (37846).
The user the mileage is registered for. Omit (or send your own UserID) to register for yourself.
Refers to UserRead.UserID.
Default: the caller
- ≤ 0 — 400, Code 200.
The car used. A personal car must belong to the registration's owner; a shared car (no employee) is accepted.
Refers to CarRead.CarID. Look up valid values with GET /v1/car.
Valid values come from GET /v1/car.
The mileage rate the distance is reimbursed and invoiced at. Must have a rate period covering Date.
Refers to MileageRateRead.MileageRateID. Look up valid values with GET /v1/mileage-rate.
true = the trip was there and back; the stored total distance is Distance × 2.
Optional link to a time registration (for example the travel time of the same trip).
Refers to TimeRegistrationRead.TimeRegistrationID.
- another mileage registration already links to it — 37825 (HTTP 500, Code 102).
No check that the time registration exists or belongs to the same owner or project.
Whether the mileage is billable to the customer.
-
account features Billable AND BillableExpense are on — applied
-
either feature is off — Replaced by the resolved contract's IsMileageBillable.
Optional travel time to register together with the mileage (POST /v1/mileage-registration only).
-
sent to POST /v1/mileage-registration — A TravelTime time registration is created first (owner, Date, Purpose as comment) and linked via TimeRegistrationID; it is deleted again if the mileage save fails. TimeRegistrationID in the body is then overwritten.
-
sent to POST /v1/project-header/mileage-registration — That route binds MileageRegistrationApiCreateModelForProject, which has no such property.
Responses
A mileage (car travel) registration: the trip (date, purpose, addresses, one-way distance, round-trip flag, odometer readings), the project and contract it is booked on, the owner (UserID), car and mileage rate, approval / invoicing state, and the optional linked travel-time registration. Money is not on this object — the reimbursement and invoice amounts are derived from TotalDistance and the mileage rate's period rates. Nested Project, Car and MileageRate are filled only on $expand. Each entity carries a self link to GET /v1/mileage-registration/{id} and the mileage TAF actions.