> ## Documentation Index
> Fetch the complete documentation index at: https://help.gorelo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Logs time against a ticket.

> StartedOn and EndedOn are single UTC instants and ActualHours is decimal hours. Supply any two
of the three and the third is derived; sending all three is accepted only when ActualHours
agrees with the span between the two instants.


The logged duration is rounded up by the work type's minimum and increment, so the billed
duration can exceed the elapsed time between start and end. Supplying a ServiceLineId
prices the entry against that contract and moves its hour allowance: a BlockHours contract
has the entry's hours drawn down, and a LimitedHours contract covers the entry up to its
remaining allowance and bills only the overage beyond it.


The entry is billed to the ticket's client; the client is not sent in the request. A ticket
with no client is rejected with 409, as it is in the app, which blocks logging time on such a
ticket. Assign a client to the ticket and the same request succeeds.


A client on credit hold cannot have time logged against it and is rejected with 409, as it is
in the app, which disables Add Time for such a client. Clear the credit hold and the same
request succeeds.


A closed ticket is rejected with 409 for the same reason: the app disables Add Time once a
ticket is closed. Solved, Merged and Cancelled tickets still accept time. Reopen the ticket
and the same request succeeds.




## OpenAPI

````yaml /api-reference/openapi.json post /v1/time-entries
openapi: 3.0.4
info:
  title: Public API
  description: Gorelo Public API documentation.
  version: 1.0.0
servers:
  - url: https://api.usw.gorelo.io
    description: US
  - url: https://api.aue.gorelo.io
    description: Australia
security:
  - ApiKeyAuth: []
tags:
  - name: Attachments
    description: "Uploading a file, for anything that can carry one. One endpoint rather than one per record\r\ntype, because the upload itself is identical - only the record it is checked against differs."
  - name: Alerts
    description: Alerts management operations
  - name: Assets
    description: Assets management operations
  - name: Billing
    description: Billing management operations
  - name: Tickets
    description: Tickets management operations
  - name: Clients
    description: Clients management operations
  - name: Contacts
    description: Contacts management operations
  - name: Deletions
    description: Deletions management operations
  - name: Documents
    description: Documents management operations
  - name: Forms
    description: Forms management operations
  - name: Organization
    description: Organization management operations
  - name: Projects
    description: Projects management operations
paths:
  /v1/time-entries:
    post:
      tags:
        - Billing
        - Tickets
      summary: Logs time against a ticket.
      description: "StartedOn and EndedOn are single UTC instants and ActualHours is decimal hours. Supply any two\r\nof the three and the third is derived; sending all three is accepted only when ActualHours\r\nagrees with the span between the two instants.\r\n\n\r\nThe logged duration is rounded up by the work type's minimum and increment, so the billed\r\nduration can exceed the elapsed time between start and end. Supplying a ServiceLineId\r\nprices the entry against that contract and moves its hour allowance: a BlockHours contract\r\nhas the entry's hours drawn down, and a LimitedHours contract covers the entry up to its\r\nremaining allowance and bills only the overage beyond it.\r\n\n\r\nThe entry is billed to the ticket's client; the client is not sent in the request. A ticket\r\nwith no client is rejected with 409, as it is in the app, which blocks logging time on such a\r\nticket. Assign a client to the ticket and the same request succeeds.\r\n\n\r\nA client on credit hold cannot have time logged against it and is rejected with 409, as it is\r\nin the app, which disables Add Time for such a client. Clear the credit hold and the same\r\nrequest succeeds.\r\n\n\r\nA closed ticket is rejected with 409 for the same reason: the app disables Add Time once a\r\nticket is closed. Solved, Merged and Cancelled tickets still accept time. Reopen the ticket\r\nand the same request succeeds.\r\n"
      operationId: post_v1_time-entries
      requestBody:
        description: Time entry to create, including the ticket to log it against.
        content:
          application/json-patch+json:
            schema:
              $ref: '#/components/schemas/CreateTimeEntryCommand'
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTimeEntryCommand'
          text/json:
            schema:
              $ref: '#/components/schemas/CreateTimeEntryCommand'
          application/*+json:
            schema:
              $ref: '#/components/schemas/CreateTimeEntryCommand'
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/CreateTimeEntryResultBaseResponse'
            application/json:
              schema:
                $ref: '#/components/schemas/CreateTimeEntryResultBaseResponse'
            text/json:
              schema:
                $ref: '#/components/schemas/CreateTimeEntryResultBaseResponse'
        '400':
          description: Bad Request
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            application/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            text/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            application/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            text/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
        '404':
          description: Not Found
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            application/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            text/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
        '409':
          description: Conflict
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            application/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            text/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
        '500':
          description: Internal Server Error
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            application/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
            text/json:
              schema:
                $ref: '#/components/schemas/ObjectBaseResponse'
components:
  schemas:
    CreateTimeEntryCommand:
      type: object
      properties:
        TicketId:
          type: string
          description: "Ticket to log the time against. Send **either** this or `TaskId`, not both and\r\nnot neither. Validated against the service provider - an unknown ticket is a 404. The entry\r\nis billed to the ticket's client; a ticket with no client is rejected with 409."
          format: uuid
          nullable: true
          example: e5fb2939-3bd7-4893-8b47-a287003d5b8a
        TaskId:
          type: string
          description: "Task to log the time against, instead of a ticket. Validated the same way - an unknown task\r\nis a 404. The entry is billed to the client of the task's **project**, which is\r\nauthoritative; the task's own copy of the client can lag behind.\r\n            \r\nA task carries no billing defaults of its own, so the billing role, work type and billable\r\nstatus fall to the technician's defaults and then the service provider's - the same chain a\r\nticket uses once its own values are absent."
          format: uuid
          nullable: true
        StartedOn:
          type: string
          description: "When the work started, as a single UTC instant.\r\n            \r\nSupply any two of StartedOn, EndedOn and ActualHours and the third is derived. Supplying all\r\nthree is accepted only when ActualHours agrees with the span between the two instants, to the\r\nsame four decimal places GET reports it at; a disagreement is rejected rather than resolved\r\nby discarding one of the three."
          format: date-time
          nullable: true
          example: '2026-04-28T09:00:00.0000000+00:00'
        EndedOn:
          type: string
          description: "When the work finished, as a single UTC instant. Must be later than `StartedOn`.\r\nSee `StartedOn` for which combinations of the three time fields are accepted."
          format: date-time
          nullable: true
          example: '2026-04-28T10:30:00.0000000+00:00'
        ActualHours:
          type: number
          description: "Duration worked, in decimal hours. Must be greater than 0.\r\n            \r\nThis is the raw logged duration and is never pre-rounded: the work type's minimum and\r\nincrement rounding is applied on top of it, and GET reports the rounded figure separately as\r\nAdjustedHours. See `StartedOn` for which combinations of the three time fields\r\nare accepted."
          format: double
          nullable: true
          example: 1.5
        BillableStatusId:
          type: integer
          description: "Billable status for this entry: 1 = Billable, 2 = No charge, 3 = Non-billable; any other\r\nvalue is rejected. Optional — omit it and the ticket's own billable status is used, else the\r\nwork type's, else Billable."
          format: int32
          nullable: true
          example: 1
        UserId:
          type: integer
          description: "User whose time this is. Validated against the service provider. Named UserId rather than\r\nTechnicianId because Gorelo uses \"user\", not \"technician\", in its own vocabulary."
          format: int64
          example: 8842
        BillingRoleId:
          type: integer
          description: "Billing role to charge at. Optional — omit it and the ticket's own billing role is used, else\r\nthe user's, else the service provider's first. Get valid ids from\r\nGET /v1/billing-roles."
          format: int64
          nullable: true
          example: 1742
        WorkTypeId:
          type: integer
          description: "Work type to apply. Determines the rate multiplier and the minimum/increment rounding applied\r\nto the logged duration. Optional — omit it and the ticket's own work type is used, else the\r\nuser's, else the service provider's first. Get valid ids from\r\nGET /v1/work-types."
          format: int64
          nullable: true
          example: 2432
        ServiceLineId:
          type: integer
          description: "Service line to bill this time against.\r\n            \r\nOptional. Omit it and the ticket's own service line is used, else the first one whose\r\nconfigured billing roles and work types both cover the resolved role and type, else no\r\ncontract. Send it as null to decline a contract outright, which suppresses the automatic\r\ncontract assignment that would otherwise happen when the time is approved.\r\n            \r\nWhen supplied, the service line must belong to the ticket's client, and its contract must\r\nbe active today and cover labour."
          format: int64
          nullable: true
          example: 4720
        Comment:
          type: string
          description: Free-text note describing the work.
          nullable: true
          example: Replaced faulty NIC
        Distance:
          type: number
          description: >-
            Distance travelled for this work, when the service provider bills
            mileage.
          format: double
          nullable: true
          example: 12.5
        Attachments:
          type: array
          items:
            $ref: '#/components/schemas/TimeEntryAttachmentModel'
          description: >-
            Files to attach to this entry. The serialised list must be 5000
            characters or fewer.
          nullable: true
      additionalProperties: false
      description: Logs time against a ticket.
    CreateTimeEntryResultBaseResponse:
      type: object
      properties:
        StatusCode:
          $ref: '#/components/schemas/HttpStatusCode'
        IsSuccess:
          type: boolean
        Data:
          $ref: '#/components/schemas/CreateTimeEntryResult'
        DataContext:
          type: object
          additionalProperties: false
          nullable: true
          example: null
        Notifications:
          type: array
          items:
            $ref: '#/components/schemas/Error'
      additionalProperties: false
    ObjectBaseResponse:
      type: object
      properties:
        StatusCode:
          $ref: '#/components/schemas/HttpStatusCode'
        IsSuccess:
          type: boolean
        Data:
          nullable: true
        DataContext:
          $ref: '#/components/schemas/ErrorDataContext'
        Notifications:
          type: array
          items:
            $ref: '#/components/schemas/Error'
      additionalProperties: false
    TimeEntryAttachmentModel:
      type: object
      properties:
        Name:
          type: string
          description: File name as it should be displayed.
          example: switch-photo.jpg
        Url:
          type: string
          description: Location the file can be fetched from.
          example: https://files.example.com/switch-photo.jpg
      additionalProperties: false
    HttpStatusCode:
      enum:
        - 100
        - 101
        - 102
        - 103
        - 200
        - 201
        - 202
        - 203
        - 204
        - 205
        - 206
        - 207
        - 208
        - 226
        - 300
        - 301
        - 302
        - 303
        - 304
        - 305
        - 306
        - 307
        - 308
        - 400
        - 401
        - 402
        - 403
        - 404
        - 405
        - 406
        - 407
        - 408
        - 409
        - 410
        - 411
        - 412
        - 413
        - 414
        - 415
        - 416
        - 417
        - 421
        - 422
        - 423
        - 424
        - 426
        - 428
        - 429
        - 431
        - 451
        - 500
        - 501
        - 502
        - 503
        - 504
        - 505
        - 506
        - 507
        - 508
        - 510
        - 511
      type: integer
      format: int32
    CreateTimeEntryResult:
      type: object
      properties:
        Id:
          type: integer
          description: Identifier of the created time entry.
          format: int64
          example: 904412
      additionalProperties: false
    Error:
      type: object
      properties:
        Code:
          pattern: ^[0-9]{6}$
          type: string
          description: 6-digit MMTTNN error code. See the Public API error-code registry.
          example: '070101'
        Message:
          type: string
        PropertyName:
          type: string
          nullable: true
        ActionHint:
          type: string
          nullable: true
        DocUrl:
          type: string
          nullable: true
      additionalProperties: false
    ErrorDataContext:
      type: object
      properties:
        TraceId:
          type: string
          description: Request correlation id. Quote this when reporting a problem.
          nullable: true
      additionalProperties: false
      description: "The `DataContext` of an error response: carries the correlation id for the failed\r\nrequest."
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      description: >-
        Your Gorelo API key. See [Generate API key](/generate-api-key). If your
        data is hosted in Australia, click the base URL at the top of this page
        and select `https://api.aue.gorelo.io`.
      name: X-API-Key
      in: header

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.