Search customers, contacts, projects, invoices and employees
Finds customers, contacts, projects, invoices and employees whose name or number starts with the search text.
Prefix search across five categories at once, at most 20 hits per category, each hit with a display label and a link into the web UI. The caller's saved category preferences (GET /v2/search/categories) are NOT applied here — the client passes them in categories.
Use it to:
- Type-ahead search box: jump to a customer, contact, project, invoice or colleague.
Not for:
- Picking a task or project to register time on → the v1 search-for-time-tracking operations
Behaviour:
- Idempotent.
- Safe to retry.
- Changes are visible to the next read immediately.
Call first: GET /v2/search/categories
See also: GET /v1/project-header/search-for-time-tracking
Not yet documented: examples (success); request → query params.
curl -X GET "https://app3.timelog.com/your-account/api/v2/search?searchText=example_string&categories=Customer" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN"
import requests
import json
url = "https://app3.timelog.com/your-account/api/v2/search?searchText=example_string&categories=Customer"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
response = requests.get(url, headers=headers)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v2/search?searchText=example_string&categories=Customer", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://app3.timelog.com/your-account/api/v2/search?searchText=example_string&categories=Customer", nil)
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/v2/search?searchText=example_string&categories=Customer')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
response = http.request(request)
puts response.body
{
"Properties": {
"Customers": "null",
"Contacts": "null",
"Projects": "null",
"Invoices": "null",
"Employees": "null"
},
"Links": [
{
"Href": "example_string",
"Rel": "example_string"
}
],
"Actions": [
{}
]
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"Message": "No HTTP resource was found that matches the request URI 'https://app{server-number}.timelog.com/{account}/api/v2/search'.",
"MessageDetail": "No action was found on the controller 'Search' that matches the request."
}
{
"error": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v2/search
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.
> Not yet documented: how to obtain a token.
> Not yet documented: token lifetime.
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.Text the name / number must START with. '[' is escaped; '%' and '_' are NOT, so they act as wildcards ('%' matches everything). Omitted → 404 'No action was found' (captured), not 400.
Filters on SearchItemRead.Name (starts_with).
Category names, case-insensitive: Customer, Contact, Project, Invoice, Employee. The values are ADDED, not OR-ed: a repeated name or 'All' mixed with others produces a wrong set. An unknown name → 500.
Several values: one string, separated by ,.
Default: all five categories
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| Customer | Customer | Search customers (flag 1). |
| Contact | Contact | Search active contact persons (flag 2). |
| Project | Project | Search projects (flag 4). |
| Invoice | Invoice | Search invoices / vouchers (flag 8). |
| Employee | Employee | Search active users (flag 16). |
| All | All | -1: every category — only meaningful alone. |
| None | None | 0: adds nothing. |
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.
Not yet documented: how to obtain a token.
Not yet documented: token lifetime.
Query Parameters
Text the name / number must START with. '[' is escaped; '%' and '_' are NOT, so they act as wildcards ('%' matches everything). Omitted → 404 'No action was found' (captured), not 400.
Filters on SearchItemRead.Name (starts_with).
Category names, case-insensitive: Customer, Contact, Project, Invoice, Employee. The values are ADDED, not OR-ed: a repeated name or 'All' mixed with others produces a wrong set. An unknown name → 500.
Several values: one string, separated by ,.
Default: all five categories
Allowed values:
| Value | Name | Meaning |
|---|---|---|
Customer | Customer | Search customers (flag 1). |
Contact | Contact | Search active contact persons (flag 2). |
Project | Project | Search projects (flag 4). |
Invoice | Invoice | Search invoices / vouchers (flag 8). |
Employee | Employee | Search active users (flag 16). |
All | All | -1: every category — only meaningful alone. |
None | None | 0: adds nothing. |
CustomerContactProjectInvoiceEmployeeAllNoneResponses
The result of one global search, grouped by category (customers, contacts, projects, invoices, employees). Each category holds at most 20 hits. Which categories are searched, and which records the caller can find in each, depends on the caller's page access and privileges (see GET /v2/search).