> ## Client identification
>
> Requests to this API should include a `Client-Platform` header alongside
> `Authorization`, identifying the application, tool, or model making the
> request. This is used for integration support and analytics.
>
> Use your model or tool name, for example `claude-code` or `chatgpt`.
>
> Example:
>
> ```bash
> curl https://api.salaryboxapp.com/... \
>   -H "Authorization: Api-Key <api-key>" \
>   -H "Client-Platform: claude-code"
> ```

# List CRM Trip Timeline

GET https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline

Returns every trip the named staff (1 to 10) logged in Field Force CRM, with where each started and stopped, for up to 31 days. Requires the Field Force CRM add-on.

One row per staff member, active or inactive, ordered by `salarybox_id`; staff with nothing in the range are still listed. A trip counts on the day it was started. `trips` are oldest first, each with `is_valid` (whether the app's CRM metrics count it), `distance_km`, `start_trip_data` and `stop_trip_data`.

Start and stop data have `date`, `time` (with offset), `timezone`, `odometer_reading`, `latitude`, `longitude`, `address`, `notes` and `attachment_count`. `stop_trip_data` is `null` while a trip is in progress. The planned destination and attachments are not returned.


Reference: https://docs.salarybox.in/api-reference/crm/trip-timeline-retrieve

## Authentication

- `Authorization` header (required) (prefixed with ` Api-Key   `) — Format: "Api-Key \{api-key}"

## Request

### Query parameters

- `start_date` (date, required) — First day, `YYYY-MM-DD`.
- `end_date` (date, required) — Last day, `YYYY-MM-DD`. The range can cover at most 31 days.
- `salarybox_ids` (list of string, required) — Required, repeatable, 1 to 10. The staff members whose trips are returned.
- `page` (integer, optional) — Page number, starting at 1.
- `page_size` (integer, optional, default: 25) — Results per page. Default and maximum 25.

## Response

### 200

One row per requested staff member, each with the `trips` list.

- `data` (list of CrmEmployeeTripTimeline, required) — One row per staff member on this page.
- `message` (string, optional)
- `status` (boolean, optional)
- `count` (integer, optional) — Total number of results across all pages.
- `prev` (string, optional, nullable) — URL of the previous page, or null.
- `next` (string, optional, nullable) — URL of the next page, or null.
- `rate_limit` (list of string, optional) — What is left in each rate-limit window, for example `29/30 requests remaining per 5 minutes`.

## Errors

### 400 Bad Request Error

A date is missing or malformed, `end_date` is before `start_date`, the range is longer than 31 days, or more than 10 `salarybox_ids` were given.

- `message` (string, required)
- `status` (boolean, required)
- `errors` (map from string to any, optional, nullable)

### 403 Forbidden Error

The Field Force CRM add-on is not enabled.

- `message` (string, required)
- `status` (boolean, required)
- `errors` (map from string to any, optional, nullable)

### 429 Too Many Requests Error

A rate limit was exceeded (1 request per 2 seconds, 30 per 5 minutes, 100 per hour, or the shared 5000 per day). The response says how many seconds to wait.

- `any`

## Types

### CrmEmployeeTripTimeline

One staff member with their totals and every trip started in the date range (List CRM Trip Timeline). The summary form without `trips` is `CrmEmployeeTrips`.

- `salarybox_id` (string, required) — SalaryBox ID of the staff member.
- `employee_name` (string, required) — Full name of the staff member.
- `total_distance_km` (string, required) — Sum of the distance of the trips started in the range, in kilometres, with 2 decimals. `"0.00"` if none.
- `number_of_trips` (integer, required) — Number of trips started in the range. 0 if none.
- `trips` (list of CrmTrip, required) — The staff member's trips started in the range, oldest first. Empty if the person has none.

### CrmTrip

One trip. The planned destination is not returned.

- `is_valid` (boolean, required) — Whether the trip is counted as valid, as the SalaryBox app's CRM metrics treat it.
- `distance_km` (string, required) — Distance travelled, in kilometres, with 2 decimals.
- `start_trip_data` (CrmTripEvent, required, nullable) — The start of the trip, or `null` if there is no start record.
- `stop_trip_data` (CrmTripEvent, required, nullable) — The end of the trip. `null` for a trip still in progress.

### CrmTripEvent

The start or stop of a trip (the first start or stop event of the trip).

- `date` (date, required) — Date of the event, in the event's time zone.
- `time` (datetime, required, nullable) — When the event happened, ISO 8601 with a UTC offset, in the event's `timezone`.
- `timezone` (string, required) — IANA name of the time zone the event was logged in, falling back to the company time zone.
- `odometer_reading` (string, required, nullable) — Odometer reading entered at the event. `null` if not entered.
- `latitude` (string, required, nullable) — Latitude where the event was logged, as a decimal string. `null` if not captured.
- `longitude` (string, required, nullable) — Longitude where the event was logged, as a decimal string. `null` if not captured.
- `address` (string, required, nullable) — Address where the event was logged.
- `notes` (string, required, nullable) — Notes the staff member wrote at the event.
- `attachment_count` (integer, required) — Number of files attached to the event. The attachments themselves, and links to them, are not returned.

## Examples

**Response**

```json
{
  "data": [
    {
      "salarybox_id": "SBK7Q2M9TX",
      "employee_name": "Aarav Sharma",
      "total_distance_km": "14.35",
      "number_of_trips": 2,
      "trips": [
        {
          "is_valid": true,
          "distance_km": "14.35",
          "start_trip_data": {
            "date": "2026-10-28",
            "time": "2026-10-28T09:30:00+05:30",
            "timezone": "Asia/Kolkata",
            "odometer_reading": "10250.00",
            "latitude": "28.63042030",
            "longitude": "77.21669000",
            "address": "Connaught Place, New Delhi, Delhi 110001",
            "notes": "",
            "attachment_count": 1
          },
          "stop_trip_data": {
            "date": "2026-10-28",
            "time": "2026-10-28T10:20:00+05:30",
            "timezone": "Asia/Kolkata",
            "odometer_reading": "10264.35",
            "latitude": "28.54490000",
            "longitude": "77.19340000",
            "address": "Hauz Khas, New Delhi, Delhi 110016",
            "notes": "Reached the customer site",
            "attachment_count": 1
          }
        },
        {
          "is_valid": true,
          "distance_km": "0.00",
          "start_trip_data": {
            "date": "2026-10-29",
            "time": "2026-10-29T13:05:00+05:30",
            "timezone": "Asia/Kolkata",
            "odometer_reading": null,
            "latitude": "28.63042030",
            "longitude": "77.21669000",
            "address": null,
            "notes": "",
            "attachment_count": 0
          },
          "stop_trip_data": null
        }
      ]
    }
  ],
  "message": "Success",
  "status": true,
  "count": 1,
  "prev": null,
  "next": null,
  "rate_limit": [
    "0/1 requests remaining per 2 seconds",
    "29/30 requests remaining per 5 minutes",
    "99/100 requests remaining per 1 hour"
  ]
}
```

**SDK Code**

```python One completed trip and one in progress
import requests

url = "https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline"

querystring = {"end_date":"2026-10-31","salarybox_ids":"[\"string\"]","start_date":"2026-10-01"}

headers = {"Authorization": "Api-Key  <apiKey>"}

response = requests.get(url, headers=headers, params=querystring)

print(response.json())
```

```javascript One completed trip and one in progress
const url = 'https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline?end_date=2026-10-31&salarybox_ids=%5B%22string%22%5D&start_date=2026-10-01';
const options = {method: 'GET', headers: {Authorization: 'Api-Key  <apiKey>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go One completed trip and one in progress
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline?end_date=2026-10-31&salarybox_ids=%5B%22string%22%5D&start_date=2026-10-01"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Api-Key  <apiKey>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby One completed trip and one in progress
require 'uri'
require 'net/http'

url = URI("https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline?end_date=2026-10-31&salarybox_ids=%5B%22string%22%5D&start_date=2026-10-01")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Api-Key  <apiKey>'

response = http.request(request)
puts response.read_body
```

```java One completed trip and one in progress
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline?end_date=2026-10-31&salarybox_ids=%5B%22string%22%5D&start_date=2026-10-01")
  .header("Authorization", "Api-Key  <apiKey>")
  .asString();
```

```php One completed trip and one in progress
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline?end_date=2026-10-31&salarybox_ids=%5B%22string%22%5D&start_date=2026-10-01', [
  'headers' => [
    'Authorization' => 'Api-Key  <apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp One completed trip and one in progress
using RestSharp;

var client = new RestClient("https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline?end_date=2026-10-31&salarybox_ids=%5B%22string%22%5D&start_date=2026-10-01");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Api-Key  <apiKey>");
IRestResponse response = client.Execute(request);
```

```swift One completed trip and one in progress
import Foundation

let headers = ["Authorization": "Api-Key  <apiKey>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.salaryboxapp.com/v2/customer/v1/crm/trips/timeline?end_date=2026-10-31&salarybox_ids=%5B%22string%22%5D&start_date=2026-10-01")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```