TimeLog
A TimeLog create requires only project; date defaults to the server's today instead of failing, entity takes any type despite valid_types ['Task'], and a script may log for any HumanUser.
Type Schema name TimeLog, REST path slug time_logs. Project-scoped: project is a stock entity
field with valid_types: ['Project'], and a create without it is refused. 12 fields in all.
| path | result |
|---|---|
GET /entity/time_logs |
200 |
GET /entity/time_log, GET /entity/TimeLog |
200, the same rows; links.self normalises to /api/v1/entity/time_logs/<id> |
GET /entity/timelogs, GET /entity/timelog, GET /entity/timesheet_entries |
404 Entity type 'timelogs' does not exist. |
The listing is site-wide and every row is project-scoped: one unfiltered page of 200 returned rows from
three projects on the probed site. Scope a read with
[["project", "is", {"type": "Project", "id": N}]].
Identity None. TimeLog has no code, no name and no content, and asking for one is a 400.
description is the text a human reads, and cached_display_name is a server-managed copy of it. Neither
is unique. Key on id, or on the triple (entity, user, date).
| filter | result |
|---|---|
["description", "is", ...] |
matches |
["code", "is", ...] |
400 API read() TimeLog.code doesn't exist. |
["name", "is", ...] |
400 API read() TimeLog.name doesn't exist. |
Create project alone. The schema flags only id as mandatory, so its flags predict nothing here
(probe 012). duration, entity, user and date are all optional, and a create without them returns a
row that reads duration: 0, description: "New Time Log" and today's date.
| body | result |
|---|---|
{} |
400 API create() missing 'project' attribute: {} |
duration alone, date alone, entity alone |
400, the same title with the body echoed |
entity + duration |
400, the same title; a Task does not imply its project |
project alone |
201, duration 0, date today, description "New Time Log" |
project + duration |
201 |
project + entity + duration |
201 |
project + duration + date |
201 |
project as a bare integer |
400 API create() TimeLog.project expected [Hash, ... NilClass] data type(s) but got Integer: <id> |
Date date is a date field, YYYY-MM-DD (field_types/date). A create that omits it is not
refused: the server stamps its own current date. A PUT clears it.
| sent | result |
|---|---|
key omitted from the POST |
201, date set to the server's today |
"2026-01-05" |
200, stored as sent |
"2026-01-05T09:00:00Z" |
400 Invalid date format: 2026-01-05T09:00:00Z. Correct format is: 2011-01-21 |
"05/01/2026" |
400, the same title |
"" and null on a PUT |
200, reads back null |
A row with date: null still counts toward Task.time_logs_sum. On the probed site 0 of 1012 TimeLogs
hold a null date, so the state exists over REST and nothing in the web app produces it: always send the
day you mean.
Duration and the rollup duration is minutes and there is no unit anywhere on the field; read
hours_per_day from GET /preferences to render (field_types/duration). Task.time_logs_sum is the
read-only sum of the duration of every TimeLog whose entity is that Task, recomputed on every write
(entity_types/Task). Adding logs of 60, 45 and 120 to a sandbox Task moved it from 150 to 210, 255
and 375; a delete and an edit followed with plain arithmetic. time_vs_est is est_in_mins - time_logs_sum
and goes negative when nothing is estimated.
Links No field anywhere in the 114 types of /schema names TimeLog specifically, so the direction is
always TimeLog to Task; 51 generic any-entity fields list it among 100+ valid_types
(field_types/entity).
| field | data type | editable | valid_types |
|---|---|---|---|
entity |
entity | yes | ['Task'] |
user |
entity | yes | ['HumanUser'] |
project |
entity | yes | ['Project'] |
created_by, updated_by |
entity | no | ['HumanUser', 'ApiUser'] |
valid_types on entity does not bind, matching field_types/entity: a Shot and a Project were both
accepted at 201 and read back as their own type. user is enforced.
| write | result |
|---|---|
user = a HumanUser other than the authenticated script |
201; user is that person, created_by stays the ApiUser |
user = an ApiUser |
400 Invalid field value, update failed [5 - Update failed for [TimeLog.user]: HumanUser expected, got ApiUser] |
user = a Project |
400, the same title, got Project |
user = a bare integer |
400 API create() TimeLog.user expected [Hash, ... NilClass] data type(s) but got Integer: <id> |
user = null |
201, reads back null |
entity = a Shot, entity = a Project |
201 each, stored as sent |
A tool can log on someone's behalf: set user, and created_by records the script that did it. user is
optional, and an omitted one is not filled in from the credentials. On the probed site 452 of 1012 TimeLogs
have no user at all, so filter ["user", "is_not", null] before attributing time to anyone.
Both link fields take a {type, id} hash. A bare id is 400 on read as well as on write:
API summarize() TimeLog.entity expected [Hash, ... NilClass] data type(s) but got Integer: <id>.
Read the Task and the person in one call rather than a second fetch (probe 003):
POST /entity/time_logs/_search Content-Type: application/vnd+shotgun.api3_array+json
{"filters": [["project", "is", {"type": "Project", "id": N}]],
"fields": ["duration", "date", "entity.Task.content", "user.HumanUser.login"]}
-> {"duration": 180, "date": "2026-01-30", "entity.Task.content": "<task>",
"user.HumanUser.login": "<user>"}
Status None. TimeLog has no status_list and no list field.
Traps
datenever fails a create. Omit it and the row gets the server's current date, in the site's timezone, not the day the work happened. A backfill that forgetsdatesilently lands on today.entityis not restricted to Task despitevalid_types: ['Task']. A tool that logs against a Shot gets 201 and a row no Task rollup will ever count. Check the type client-side.Task.time_logs_sumis read-only and 400s on a write (entity_types/Task). Change the sum by creating, editing or deleting a TimeLog.created_byandcreated_atareeditable on create only: aPUTto either is 400API update() TimeLog.created_by is editable on create only.userstays editable for the row's life.