---
title: "Create cell specifications and instances from Uncountable recipes"
method: POST
path: "/integrations/uncountable/sync"
tags: ["Uncountable"]
---

# Create cell specifications and instances from Uncountable recipes

`POST /integrations/uncountable/sync`

## Request

- Base URL: `https://api.ionworks.com`
- URL: `https://api.ionworks.com/integrations/uncountable/sync`
- Auth: none declared

Read the organization's Uncountable project and create what is missing.

Callable with an API key (``X-API-Key``) as well as a session, so a scheduler
can run it. Idempotent: specifications are matched by name within the project
and instances by recipe id within their specification, and existing records
are left unchanged. With ``dry_run`` it writes nothing and reports what it
would create, with the field mapping. Answers 403 unless the organization's
``nav_config.uncountableSync`` is enabled and names its projects.

## Request body

- UncountableSyncRequest
  - `project_id` string, uuid, required: Ionworks project the specifications and instances go into.
  - `dry_run` boolean: Report what a sync would create and reuse, and how fields map, without writing anything.
  - `accept` object: Existing specifications and instances whose Uncountable changes to write: id -> the ``fingerprint`` a dry run showed for them. A record whose changes no longer match it is not written but reported as ``stale``, so only the values someone reviewed are ever written.

## Response `200`

Successful Response

- UncountableSyncResult
  - `uncountable_project_ids` integer[], required: Uncountable projects read.
  - `recipes_read` integer, required: Distinct recipes across the Uncountable projects.
  - `recipes_skipped` integer, required: Full-cell recipes skipped because they have no inputs yet.
  - `refused` string[]: Recipes and designs that could not be mapped without guessing (two slurries for one electrode, or no design capacity), and why. Nothing is created for them. One entry can stand for a whole design.
  - `recipes_refused` integer: Recipes behind the refusals, counting every recipe of a design.
  - `specs_created` integer, required: New cell specifications.
  - `specs_existing` integer, required: Cell specifications reused.
  - `instances_created` integer, required: New cell instances.
  - `instances_existing` integer, required: Cell instances already there.
  - `specs_changed` integer: Existing specs Uncountable now states differently.
  - `instances_changed` integer: Existing instances Uncountable now states differently.
  - `specs_updated` integer: Changed specs written.
  - `instances_updated` integer: Changed instances written.
  - `changes_stale` integer: Accepted specs and instances not written because Uncountable or Ionworks changed them since the preview. Preview again to review them.
  - `changes_failed` integer: Accepted specs and instances that could not be written; ``specs[].failures`` says why.
  - `specs` UncountableSyncSpec[], required: Per-spec breakdown.
    - `changes` UncountableFieldChange[]: Where Uncountable now differs from the existing spec's design.
      - `field` string, required: ``ratings.<name>``, ``spec.<name>``, ``<slot>.<name>`` (``<slot>.material`` for the material), or ``notes``.
      - `current` unknown
      - `incoming` unknown
    - `id` string, nullable, required: Cell specification id; None for one a dry run would create.
    - `name` string, required: Cell specification name.
    - `created` boolean, required: False when an existing spec was reused.
    - `instances_created` integer, required: New instances under this spec.
    - `instances_existing` integer, required: Instances that already existed and were left unchanged.
    - `conflicts` object: Component fields on which the recipes grouped into this spec disagree, with the distinct values seen. Non-empty means the design grouping is coarser than the recipes; the first stated value was used.
    - `gaps` object: Component fields some grouped recipes do not state, with the recipe ids lacking them. The value is unknown for those cells, so the value the other recipes state was used.
    - `updated` boolean: True when those changes were written.
    - `stale` boolean: Accepted with a fingerprint its changes no longer match, so not written.
    - `instances_changed` integer: Existing instances Uncountable now states differently.
    - `instances_updated` integer: Of those, the ones whose changes were written.
    - `instances_stale` integer: Of those, accepted ones not written because they moved.
    - `failures` string[]: Accepted changes that could not be written, each with the reason; the rest of the sync went ahead.
    - `design` object, nullable: Dry run only: the specification a fresh import would write (ratings, form factor, properties, components).
    - `instances` UncountableSyncInstance[]: Dry run only: every instance of the spec, new or existing.
      - `changes` UncountableFieldChange[]: Where Uncountable now differs from the existing instance.
        - `field` string, required: ``ratings.<name>``, ``spec.<name>``, ``<slot>.<name>`` (``<slot>.material`` for the material), or ``notes``.
        - `current` unknown
        - `incoming` unknown
      - `name` string, required: Instance name: the Uncountable recipe id.
      - `notes` string, required: Instance notes: the recipe tag and name.
      - `measured_properties` object: What the instance would carry.
      - `exists` boolean, required: True when the cell is already in Ionworks: in this spec by name, or in another by its recipe tag (see ``moved_from``).
      - `id` string, nullable: The existing instance's id.
      - `moved_from` string, nullable: The spec the cell is in when its recipe now maps to this one. Cells are not moved between specs, so it stays where it is.
      - `fingerprint` string, nullable, required: Digest of ``changes``; send it back in ``accept`` to write them.
    - `fingerprint` string, nullable, required: Digest of ``changes``; send it back in ``accept`` to write them.
  - `dry_run` boolean: True when nothing was written: the counts are a preview.
  - `mapping` UncountableMappingRow[]: How Uncountable fields land in Ionworks.
    - `source` string, required: The Uncountable field, by its name there.
    - `input_ids` integer[]: Uncountable input ids it is read from; empty for recipe fields.
    - `target` string, required: Where it lands in Ionworks.
    - `note` string: How the value is read or derived.
  - `unmapped_inputs` UncountableUnmappedInput[]: Inputs the full-cell recipes state that are not imported.
    - `input_id` integer, required
    - `name` string, required
    - `recipes` integer, required: Full-cell recipes that state it.

## Other responses

- `422`: Validation Error

## Changes

- **2026-10-15** `c1374d49d42d`: 1 warning, 17 info
  - removed the optional property `specs/items/drift` from the response with the `200` status
  - added the new optional request property `accept`
  - added the optional property `changes_failed` to the response with the `200` status
  - added the optional property `changes_stale` to the response with the `200` status
  - …14 more
- **2026-10-02** `af9fdad1dc5b`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/ionworks/apis/ionworks-api/changes/integrations/uncountable/sync/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)
