---
title: "Record a cycler's open service as done"
method: POST
path: "/cyclers/{cycler_id}/service_events/complete"
tags: ["Cyclers"]
---

# Record a cycler's open service as done

`POST /cyclers/{cycler_id}/service_events/complete`

## Request

- Base URL: `https://api.ionworks.com`
- URL: `https://api.ionworks.com/cyclers/{cycler_id}/service_events/complete`
- Auth: none declared

Record service as done on a cycler.

Two paths behind one endpoint, chosen by whether the cycler is currently out
of service:

- **Open event** (it was taken out first): that event is completed and every
  channel incident pointing at it is closed in one write, so the cycler
  cannot end up half returned to service.
- **No open event**: the common case of "I calibrated this, log it". A
  single already-completed event is recorded from the request's
  ``event_type``. Nothing was taken out, so nothing needs returning, and no
  one has to open an event purely so it can be closed.

Either way a ``calibration`` advances ``last_calibrated_at`` — the only
supported way that field moves — and optionally updates
``calibration_interval_days``.

Addressed by cycler rather than by event id because a cycler has at most one
open event: the caller already knows which cycler it serviced, and looking
up the event id first would be a round-trip that proves nothing.

## Path parameters

- `cycler_id` string, uuid, required

## Request body

- object

## Response `200`

Successful Response

- CyclerServiceEvent: One instrument-level service on a cycler. A service event is the thing a channel incident belongs to when the whole cycler is down. Calibration is performed on the *instrument*, so a 40-channel cycler going in for its annual calibration is one event, not 40 unrelated outages that happen to share a category. Channel incidents opened for the event carry its id in ``service_event_id``, which is what lets one action close them together. At most one event per cycler may be open (``performed_at`` null) — a unique partial index enforces it, so "the current service" is a single unambiguous row and the open-event write path is safe to retry.
  - `id` string, required: Unique identifier for the service event
  - `organization_id` string, required: Organization this service event belongs to.
  - `project_id` string, required: Project that owns the cycler (pinned to it by FK)
  - `cycler_id` string, required: The cycler being serviced
  - `event_type` 'calibration' | 'preventive_maintenance' | 'firmware' | 'repair' | 'other', required: What kind of work took (or will take) a cycler out of service. Mirrors the ``cycler_service_events_shape_check`` CHECK constraint. Stored as CHECK-constrained text rather than a Postgres enum so the allowed set can be widened by swapping a constraint — ``ALTER TYPE ... ADD VALUE`` does not compose with the additive-migration rule. ``calibration`` is the one that feeds ``cyclers.last_calibrated_at``: completing an event of this type is what advances the calibration clock.
  - `scheduled_for` string, date-time, nullable: When the visit is booked. Required while the event is open; null is allowed on an event recorded after the fact.
  - `scheduled_until` string, date-time, nullable: When the booked visit is expected to end, making the booking a span rather than a start instant. Null on rows predating the column and on events recorded after the fact. A plan, not a constraint: a visit may legitimately run past it.
  - `performed_at` string, date-time, nullable: When the service was actually done. Null means the event is still open — at most one such event per cycler.
  - `notes` string, nullable: Free-text detail about the service
  - `created_by` string, nullable: User who opened or recorded the event
  - `performed_by` string, nullable: User who recorded the service as done
  - `created_at` string, date-time, required: Timestamp when the event row was created
  - `updated_at` string, date-time, required: Timestamp when the event row was last updated

## Other responses

- `422`: Validation Error

## Changes

- **2026-09-30** `ea3d377fec1a`: 1 breaking
  - for the `path` request parameter `cycler_id`, the format changed from no format to `uuid`

[Operation history](https://skmtc.dev/ionworks/apis/ionworks-api/changes/cyclers/:cycler_id/service_events/complete/post.md)

---

[API](https://skmtc.dev/ionworks/apis/ionworks-api.md) - [All operations](https://skmtc.dev/ionworks/apis/ionworks-api/llms.txt) - [OpenAPI document](https://skmtc.dev/ionworks/apis/ionworks-api/revisions/abebbbc5f60a?raw)
