Factuarea APIDevelopers

Time logged on tasks

Log time on tasks by hand or with a timer, read the time summary of a project and turn billable hours into a draft invoice — and why it is not the working-day clock-in.

Time logged on a task measures the work spent on it, so you can see where the hours go and bill them. You log it by hand, as a closed period, or with a timer that runs until you stop it, and a project turns its billable hours into a draft invoice.

This is not the working-day register. A task time entry belongs to the tasks module: it uses the tasks:* scopes, its events are task_time_entry.* and its operations live under /v1/tasks/{task}/time-entries and /v1/task-timers. The clock-ins of the Time tracking module (/v1/time-entries, time_entries:*, the control_horario module) are the legal, hash-chained working-day record — a different resource with its own register. Hours logged on a task never appear in that register, in its balances, in monthly closes or in payroll exports. See Time tracking.

Log time by hand

POST /v1/tasks/{task}/time-entries logs a closed period of work:

curl -X POST https://api.factuarea.com/v1/tasks/$TASK_ID/time-entries \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "started_at": "2026-09-28T09:00:00+02:00",
    "ended_at": "2026-09-28T11:00:00+02:00",
    "description": "Payment gateway integration",
    "billable": true
  }'
  • started_at and ended_at are ISO 8601 with a time zone. The period must end after it starts, last at most 24 hours and not start more than a day in the future; otherwise the call returns 422 invalid_time_entry_period. An entry with equal start and end is valid and lasts zero seconds.
  • description is optional, up to 1,000 characters. billable defaults to true.
  • The author is always the holder of the credential.
  • Entries may overlap: this is work you attribute to tasks, not a clock-in.

The entry comes back with its duration_seconds, is_running, and, once it is billed, the invoice_id and the invoiced_at of the invoice that includes it.

The timer

Instead of typing a period, start a timer when you begin and stop it when you finish:

# Start it on a task (no body)
curl -X POST https://api.factuarea.com/v1/tasks/$TASK_ID/timer/start \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"

# What is running right now? `data` is null when nothing is
curl https://api.factuarea.com/v1/task-timers/current \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"

# Stop it, whichever task it runs on
curl -X POST https://api.factuarea.com/v1/task-timers/stop \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"
  • One timer per user and company. Starting another while one runs, on any task, returns 409 task_timer_already_running and leaves the running one untouched. A user with a timer in another company can start one here.
  • A running timer is an entry with ended_at: null and is_running: true.
  • Stopping fixes the end at the current instant and returns the closed entry. With no timer running it returns 422 task_timer_not_running.
  • A timer left running for more than 24 hours is closed at exactly 24 hours after its start, so a forgotten timer never blocks the next one.
  • The timer belongs to the holder of the credential, so GET /v1/task-timers/current shows their timer and nobody else's.

Edit and delete entries

PUT /v1/tasks/{task}/time-entries/{time_entry} is partial and changes started_at, ended_at, description and billable. On a running entry you can only change the description and the billable flag: stop the timer to close it.

Once an entry is included in an invoice it is locked: editing or deleting it returns 422 task_time_entry_invoiced until the draft invoice is deleted. DELETE .../time-entries/{time_entry} is permanent and needs the tasks:delete scope and an Idempotency-Key; deleting a running entry cancels the timer.

GET /v1/tasks/{task}/time-entries lists the entries of a task, newest first and cursor-paginated.

Time summary of a project

GET /v1/projects/{project}/time-summary answers how much time a project has consumed, optionally between from and to (dates in the company time zone):

{
  "data": {
    "object": "project_time_summary",
    "project_id": "0193a4f2-7c20-7a11-8b52-4d6e8f0a2c01",
    "from": "2026-09-01",
    "to": "2026-09-30",
    "total_seconds": 36900,
    "billable_seconds": 33300,
    "invoiced_seconds": 9900,
    "pending_seconds": 23400,
    "by_task": [
      {
        "task_id": "0193a4f2-8f53-7d44-8e85-7a9b1c3d5f12",
        "key": "DEV-12",
        "title": "Integrate the payment gateway in the checkout",
        "total_seconds": 22500,
        "billable_seconds": 22500,
        "invoiced_seconds": 9900,
        "pending_seconds": 12600
      }
    ],
    "by_user": [
      {
        "user_id": "0193a4f2-6b1c-7d3e-8a41-2c5e7f9a1b01",
        "name": "Ana Pérez",
        "total_seconds": 22500,
        "billable_seconds": 22500
      }
    ]
  }
}

pending_seconds is what you can still bill: the billable time not yet invoiced. Running timers are left out of every total.

Invoice the hours

A project turns its billable hours into a draft invoice for its billing contact. Two things must be set on the project, with PUT /v1/projects/{project} or in the app:

  • a billing contact (billing_contact_id) that can receive sales invoices;
  • a price: an hourly rate (billing_hourly_rate) or a billing product (billing_product_id), whose price for that contact is used when there is no rate.

Without them, the call returns 422 task_time_not_invoiceable and says what is missing.

Choose the hours with either entry_ids (up to 500 entries) or a period (from and to, inclusive, by the start date of the entries) — never both — and a grouping: per_task gives one line per task, per_entry one line per entry and single_line a single line with the total. Only closed, billable, not yet invoiced entries of the project qualify.

Preview first. It applies the same checks and creates nothing:

curl -X POST https://api.factuarea.com/v1/projects/$PROJECT_ID/time-invoices/preview \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30" }, "grouping": "per_task" }'

The preview lists the customer, the lines (quantity in hours, unit HUR, price and amount before taxes), the totals in seconds and hours, the entries that would be invoiced and any warnings. Then create the invoice with the same body:

curl -X POST https://api.factuarea.com/v1/projects/$PROJECT_ID/time-invoices \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30" }, "grouping": "per_task" }'
{
  "data": {
    "id": "0193a4f2-c3fd-7bee-8c8f-7e9f1a3b5d01",
    "object": "task_time_invoice",
    "project_id": "0193a4f2-7c20-7a11-8b52-4d6e8f0a2c01",
    "invoice_id": "0193a4f2-c3fd-7bee-8c8f-7e9f1a3b5d10",
    "grouping": "per_task",
    "status": "draft"
  }
}
  • It needs invoices:write, not a project scope: the operation creates an invoice. A credential with only projects:write gets 403 insufficient_scope. The preview is a read and needs projects:read.
  • The invoice is a draft. It is never issued, sent or filed with the AEAT on its own; you finish it with the invoices API. The normal invoice rules apply: if the plan has used up its invoices, the call returns 402 limit_exceeded and nothing is created.
  • Everything or nothing. In one transaction the invoice is created and the entries are marked as invoiced. If any step fails, no entry is marked.
  • Retries are safe. Repeating the request with the same Idempotency-Key, or with the same batch_id, returns the same invoice. Two requests at once over the same entries produce one invoice; the other gets task_time_entry_invoiced.
  • Deleting the draft releases the hours. They become pending again. Issuing, sending, collecting or annulling the invoice does not release them.

Events

Time entries emit task_time_entry.created (also when a timer starts), task_time_entry.updated (also when a timer stops), task_time_entry.deleted and task_time_entry.invoiced, one per entry of an invoiced batch. See Task events.

Next steps

On this page

Need a hand?Contact support