{"openapi":"3.1.0","info":{"title":"Tiemio read-only API","version":"1.0.0","description":"Read your company's working-time records with a read-only API key (`tmk_…`) the owner creates on the Company page.\n\nGuide: https://tiemio.com/developers\n\nSend `Authorization: Bearer <key>`. 60 requests a minute. Errors are `{ error, code }`; a 500 may carry no `code`.\n\nOnly the fields listed here are promised. They stay; new ones may appear, so ignore what you do not know."},"servers":[{"url":"https://tiemio.com","description":"Production."}],"security":[{"apiKey":[]}],"paths":{"/api/employer":{"get":{"security":[{"apiKey":[]}],"summary":"The owner's company and team","description":"Company and each employment, with each active person's clock now: state, since when, today's and this month's worked time. With an API key, contact details are null.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"employer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"taxId":{"type":["string","null"]},"timeZone":{"type":"string","description":"IANA zone that cuts the company's days."},"closedAt":{"anyOf":[{"type":"string","description":"ISO 8601 instant, UTC."},{"type":"null"}]}},"required":["id","name","taxId","timeZone","closedAt"],"additionalProperties":{}},"employments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The employment id, used by the day and chain routes."},"displayName":{"type":"string","description":"Empty when the person gave no name."},"status":{"type":"string","enum":["invited","requested","active","ended"]},"activatedAt":{"anyOf":[{"type":"string","description":"ISO 8601 instant, UTC."},{"type":"null"}]},"clock":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["off","working","on_break"]},"shiftStartedAt":{"anyOf":[{"type":"string","description":"ISO 8601 instant, UTC."},{"type":"null"}]},"since":{"anyOf":[{"type":"string","description":"ISO 8601 instant, UTC."},{"type":"null"}]},"todayWorkedMs":{"type":"integer"},"monthWorkedMs":{"type":"integer"}},"required":["state","shiftStartedAt","since","todayWorkedMs","monthWorkedMs"],"additionalProperties":{}},{"type":"null"}],"description":"Active people only."},"timetable":{"anyOf":[{"type":"object","properties":{"from":{"type":"string","description":"YYYY-MM-DD in the company's time zone."},"id":{"type":["string","null"]},"name":{"type":["string","null"]}},"required":["from","id","name"],"additionalProperties":{}},{"type":"null"}],"description":"The timetable followed today."}},"required":["id","displayName","status","activatedAt","clock","timetable"],"additionalProperties":{}}}},"required":["employer","employments"],"additionalProperties":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/employer/month":{"get":{"security":[{"apiKey":[]}],"summary":"The team's month","description":"Worked time per person per day (the hours calendar). Totals use the effective record: approved corrections applied.","parameters":[{"name":"month","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"description":"YYYY-MM, not in the future. Default: this month."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string","description":"YYYY-MM."},"today":{"type":"string","description":"YYYY-MM-DD in the company's time zone."},"timeZone":{"type":"string"},"days":{"type":"integer","description":"Days in the month: the length of workedMs and flagged."},"people":{"type":"array","items":{"type":"object","properties":{"employmentId":{"type":"string"},"personId":{"description":"The same for someone who left and rejoined, who has a row per employment.","type":["string","null"]},"name":{"type":"string"},"from":{"anyOf":[{"type":"string","description":"YYYY-MM-DD in the company's time zone."},{"type":"null"}],"description":"First day employed."},"until":{"anyOf":[{"type":"string","description":"YYYY-MM-DD in the company's time zone."},{"type":"null"}],"description":"Last day employed, or null."},"workedMs":{"type":"array","items":{"type":"integer"},"description":"Worked milliseconds per day, index 0 = the 1st. Approved corrections applied."},"flagged":{"type":"array","items":{"type":"boolean"},"description":"Per day: true when time that day falls outside the person's timetable."}},"required":["employmentId","personId","name","from","until","workedMs","flagged"],"additionalProperties":{}}}},"required":["month","today","timeZone","days","people"],"additionalProperties":{}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/employer/employments/{id}/day":{"get":{"security":[{"apiKey":[]}],"summary":"One person's day, with how each tap was recorded","description":"The day's events (taps and approved corrections), each with device time, receipt time, whether it was queued offline, and the platform. With an API key, `notes` is always empty.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The employment id."},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD in the company's time zone, not in the future."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD in the company's time zone."},"start":{"type":"string","description":"When the day starts in the company's time zone."},"end":{"type":"string","description":"ISO 8601 instant, UTC."},"startState":{"type":"string","enum":["off","working","on_break"],"description":"The clock at `start`: a shift carried over midnight is `working`."},"workedMs":{"type":"integer","description":"Approved corrections applied."},"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["start","break_start","break_end","stop"]},"at":{"type":"string","description":"The time that counts."},"atSource":{"type":"string","enum":["server","device"]},"deviceAt":{"type":"string","description":"ISO 8601 instant, UTC."},"receivedAt":{"type":"string","description":"ISO 8601 instant, UTC."},"queued":{"type":"boolean","description":"Made offline and sent later."},"source":{"type":"string","enum":["web","ios","android","unknown"]},"corrected":{"description":"Present and true on a tap an approved correction put in place.","type":"boolean"},"correctionId":{"type":"string"}},"required":["id","type","at","atSource","deviceAt","receivedAt","queued","source"],"additionalProperties":{}}},"flags":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["outside","restricted"]},"from":{"type":"string","description":"ISO 8601 instant, UTC."},"to":{"type":"string","description":"ISO 8601 instant, UTC."}},"required":["kind","from","to"],"additionalProperties":{}}},"notes":{"type":"array","items":{},"description":"Always empty with an API key: a note's text is not available."},"corrections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"employmentId":{"type":"string"},"from":{"type":"string","description":"ISO 8601 instant, UTC."},"to":{"type":"string","description":"ISO 8601 instant, UTC."},"proposed":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["start","break_start","break_end","stop"]},"at":{"type":"string","description":"ISO 8601 instant, UTC."}},"required":["type","at"],"additionalProperties":{}}},"replaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["start","break_start","break_end","stop"]},"at":{"type":"string","description":"ISO 8601 instant, UTC."}},"required":["id","type","at"],"additionalProperties":{}}},"reason":{"description":"Written by the person who asked. Part of the traceable record.","type":["string","null"]},"requestedBy":{"type":"string","enum":["employee","employer"]},"requestedAt":{"type":"string","description":"ISO 8601 instant, UTC."},"status":{"type":"string","enum":["pending","approved","rejected","withdrawn"]},"decidedBy":{"anyOf":[{"type":"string","enum":["employee","employer"]},{"type":"null"}]},"decidedAt":{"anyOf":[{"type":"string","description":"ISO 8601 instant, UTC."},{"type":"null"}]},"decisionReason":{"type":["string","null"]},"unaccepted":{"type":"boolean","description":"An employer's proposal the employer applied without the employee's answer."},"timeZone":{"type":"string"}},"required":["id","employmentId","from","to","proposed","replaces","reason","requestedBy","requestedAt","status","decidedBy","decidedAt","decisionReason","unaccepted","timeZone"],"additionalProperties":{}},"description":"The day's decided corrections, oldest first."}},"required":["date","start","end","startState","workedMs","events","flags","notes","corrections"],"additionalProperties":{}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/employer/employments/{id}/chain":{"get":{"security":[{"apiKey":[]}],"summary":"The hash chain, to recompute","description":"Every tap as hashed, in seq order. SHA-256 (hex) of JSON.stringify([\"tiemio.time_event.v1\", id, employmentId, employerId, userId, seq, type, at, atSource, deviceAt, receivedAt, queued, source, prevHash]) equals `hash`; each `prevHash` is the previous `hash` (null for seq 1). With kind=corrections, the correction rows instead (`corrections`): SHA-256 of JSON.stringify([\"tiemio.time_correction.v1\", id, employmentId, employerId, userId, seq, kind, requestId, actorUserId, actorRole, fromAt, toAt, replaces, proposed, reason, createdAt, prevHash]). 500 per page. Both chains are checked every day against the heads last checked.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The employment id."},{"name":"after","in":"query","schema":{"type":"integer","minimum":0},"description":"Return rows with seq above this (the previous page's `next`)."},{"name":"kind","in":"query","schema":{"type":"string","enum":["events","corrections"]},"description":"Which chain: the taps (default) or the corrections."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/employer/corrections":{"get":{"security":[{"apiKey":[]}],"summary":"The company's corrections","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending"]}},{"name":"employment","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"corrections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"employmentId":{"type":"string"},"from":{"type":"string","description":"ISO 8601 instant, UTC."},"to":{"type":"string","description":"ISO 8601 instant, UTC."},"proposed":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["start","break_start","break_end","stop"]},"at":{"type":"string","description":"ISO 8601 instant, UTC."}},"required":["type","at"],"additionalProperties":{}}},"replaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["start","break_start","break_end","stop"]},"at":{"type":"string","description":"ISO 8601 instant, UTC."}},"required":["id","type","at"],"additionalProperties":{}}},"reason":{"description":"Written by the person who asked. Part of the traceable record.","type":["string","null"]},"requestedBy":{"type":"string","enum":["employee","employer"]},"requestedAt":{"type":"string","description":"ISO 8601 instant, UTC."},"status":{"type":"string","enum":["pending","approved","rejected","withdrawn"]},"decidedBy":{"anyOf":[{"type":"string","enum":["employee","employer"]},{"type":"null"}]},"decidedAt":{"anyOf":[{"type":"string","description":"ISO 8601 instant, UTC."},{"type":"null"}]},"decisionReason":{"type":["string","null"]},"unaccepted":{"type":"boolean","description":"An employer's proposal the employer applied without the employee's answer."},"timeZone":{"type":"string"}},"required":["id","employmentId","from","to","proposed","replaces","reason","requestedBy","requestedAt","status","decidedBy","decidedAt","decisionReason","unaccepted","timeZone"],"additionalProperties":{}}}},"required":["corrections"],"additionalProperties":{}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A read-only API key (`tmk_…`) an owner creates in Company settings on tiemio.com. Reads that owner's company through the operations that list it; every other route answers 401 to it. 60 requests a minute per key."}},"schemas":{"ChainPage":{"type":"object","properties":{"encoding":{"type":"string","enum":["tiemio.time_event.v1","tiemio.time_correction.v1"]},"events":{"type":"array","items":{"type":"object","description":"id, employmentId, employerId, userId, seq, type, at, atSource, deviceAt, receivedAt, queued, source, prevHash, hash (times in epoch ms)"}},"corrections":{"type":"array","items":{"type":"object","description":"With kind=corrections: id, employmentId, employerId, userId, seq, kind, requestId, actorUserId, actorRole, fromAt, toAt, replaces, proposed, reason, createdAt, prevHash, hash (times in epoch ms; replaces and proposed as stored JSON text)"}},"next":{"type":["integer","null"]}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}}}},"responses":{"BadRequest":{"description":"Malformed input (`bad_request`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"No valid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found, or not yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}