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

# Items

> Optional SKU tags that name what you sell on every invoice line

An item is a name for something you sell — "Neural Network Training -
Standard Seat," for example. You tag your billable metrics and fees with an
item so that every invoice line they produce records what was sold, not only
what was charged.

If you also keep that SKU in an accounting, CRM, or tax system, external
references link the two, and one API read then translates a whole invoice into
the codes that system expects. That mapping is a separate layer; it doesn't
change what the item records on the line.

## What's an item?

Items are **optional**. Nothing requires one: a billable metric or fee filed
directly into a product bills exactly as it always has. Paygentic doesn't
create items for you — you create them when you want your invoice lines to name
what they sold. Mapping those names to an external system is a further step.

Items are also deliberately thin. An item has:

* A **name** - the canonical, customer-facing SKU name
* A **catalog ID** - the product it belongs to, if you've filed it into one
* **Metadata** - optional key-value pairs for internal tracking
* **External references** - links to your CRM, ERP, or tax provider

An item never carries a price, currency, plan, or metering configuration.
Those live on [billable metrics](/platform/pricing/billable-metrics) and
[fees](/platform/pricing/fees), which tag an item through `itemId`.

## Creating an item

```http theme={null}
POST /v0/items
{
  "merchantId": "org_abc123",
  "name": "Neural Network Training - Standard Seat",
  "catalogId": "prod_xyz789"
}
```

`catalogId` is optional at creation time — you can file an item into a
product later by updating it, and you can move a filed item to a different
product at any time. Re-filing carries the item's tagged billable metrics
and fees to the new product with it, in one transaction.

You cannot, however, un-file an item (set `catalogId` back to `null`) while
billable metrics or fees are still tagged with it — untag them first.

## Tagging metrics and fees with an item

Billable metrics and fees accept an `itemId` that points at the item they
bill for:

```http theme={null}
POST /v0/billableMetrics
{
  "merchantId": "org_abc123",
  "name": "Tokens processed",
  "description": "Tokens processed by the model",
  "unit": "token",
  "aggregation": "SUM",
  "itemId": "itm_abc123"
}
```

Supply either `productId` or `itemId` — at least one is required. Set
`itemId` and the product is resolved from the item's `catalogId` for you; set
`productId` and the metric or fee is filed directly, with no item tag. If you
pass both, they must resolve to the same product. `productId` is always
populated on the response either way.

## From the dashboard

Everything below can be done without writing code.

<Steps>
  <Step title="Create your items">
    Open a product and go to its **Items** tab. Items you create there are filed
    under that product, which is what makes them taggable — an item belonging to no
    product cannot be attached to a charge.
  </Step>

  <Step title="Tag your charges">
    Each billable metric and fee has an optional **Item** picker on its form. It
    offers only the live items of that charge's own product, because those are the
    only ones the API will accept.
  </Step>

  <Step title="Map the item to your codes">
    Click an item's name to open it. **External mappings** lists one row per
    integration you have connected — you cannot name a system you have not connected,
    because a code for one would be read by nothing. Type a code and save; add more
    than one if the system genuinely uses more than one for that item.

    Nothing is fetched from your accounting system; you supply the codes. Each row
    says what its system expects, because the same field means different things: a
    NetSuite item internal ID is not a ledger account code, and entering one for the
    other saves cleanly and books revenue against an unrelated record.
  </Step>

  <Step title="Fix invoices that were already issued">
    On the Items tab, *"N past invoice lines are still untagged"* counts lines that
    were generated before their charge was tagged. **Apply to past invoices** fills
    them in. Read the confirmation before accepting it: amounts never change and an
    existing tag is never overwritten, but a re-export of an already-issued invoice
    will carry a code where it previously carried none.
  </Step>
</Steps>

<Note>
  **Which way the codes travel decides how many there can be.**

  Where a system *sends you* codes — a CRM's product codes arriving on an order —
  several of them may bill as one item, and each one resolves to that item alone. So
  a second item cannot take a code the first already resolves from; if you try, you
  are told which item holds it and offered to move it, which changes what future
  orders bill as and leaves issued invoices as they are.

  Where a system is *sent* a code — a ledger account — several items may share it.
  That is the point of a chart of accounts: a dozen SKUs posting to `4000 Revenue` is
  normal, not a conflict. An item may hold more than one code for such a system, as
  happens through the system's own migration, and exactly one of them is marked
  **in use** — the code its revenue actually goes out under. **Use this** moves that
  marking to another of the item's codes.

  You do not choose between these. Each integration declares what it does with
  codes, and the surface follows it.
</Note>

## Map items to your accounting system

Tagging a charge with an item records *what* was sold on every invoice line it
produces. Mapping that item to an external code records what your accounting
system calls it. Together they let one API read translate an entire invoice
into GL or SKU codes.

### 1. Record the external code

External codes live on the item as
[external references](/integrations/external-mappings), not as fields on the
item itself. One item can carry codes for several providers at once:

```bash theme={null}
curl -X POST https://api.paygentic.io/v0/externalReferences \
  -H "Authorization: Bearer $PAYGENTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "org_abc123",
    "entityType": "item",
    "entityId": "itm_abc123",
    "provider": "accountsiq",
    "externalId": "4000"
  }'
```

### 2. Read invoice lines with their codes resolved

Pass `expand=items` to any read that returns invoice lines. The response gains
an `items` collection; each line joins to it through its `itemId`:

```bash theme={null}
curl "https://api.paygentic.io/v2/invoices/inv_abc123/lineItems?expand=items&provider=accountsiq" \
  -H "Authorization: Bearer $PAYGENTIC_API_KEY"
```

```json theme={null}
{
  "invoiceId": "inv_abc123",
  "lineItems": [
    { "eventId": "li_1", "itemId": "itm_abc123", "priceId": "price_1", "totalAmount": "500.00" },
    { "eventId": "li_2", "itemId": null, "priceId": "price_2", "totalAmount": "120.00" }
  ],
  "items": [
    {
      "id": "itm_abc123",
      "name": "Neural Network Training - Standard Seat",
      "catalogId": "prod_xyz789",
      "archivedAt": null,
      "externalReferences": [
        { "provider": "accountsiq", "externalId": "4000", "isPrimary": true }
      ]
    }
  ]
}
```

The expansion is available on `GET /v2/invoices/{id}/lineItems`,
`GET /v2/invoices/lineItems`, and `GET /v2/invoices/{id}` (where asking for
`items` expands `lineItems` too, since the item IDs come from the lines).

Three things to know about the collection:

* Each distinct item appears **once**, ordered by ID. Join through `itemId`.
* It describes **the lines in that response**, not the whole invoice. If you
  page through a large invoice, combine the collections across pages. On
  `GET /v2/invoices/{id}`, asking for `items` without naming `lineItems` pages
  the lines at the default size (100), so a larger invoice returns a partial
  collection and a `nextPageToken` inside the `lineItems` block — read it.
* `provider` narrows which *references* come back, never which lines or items.
  An item with no code for that provider is returned with an empty list, so an
  unmapped SKU stays visible instead of vanishing from your reconciliation.
  A provider that isn't lowercase snake case is rejected with a `400` rather
  than answered with an empty list that would read as "nothing is mapped".
* An item can carry **more than one** reference for the same provider, and more
  than one of those can be marked `isPrimary`. The uniqueness rule runs the
  other way round — an external code is the primary reference of at most one
  item — so it does not reduce an item to one code per provider. Order is
  stable across reads, but choose deliberately rather than taking the first.

### 3. What a line tells you

The response answers two independent questions, and it helps to read them in
order. The first applies to everyone. The second only matters if you're syncing
to an external ledger.

**What was sold?** This comes from the item alone and needs no external codes.

| `itemId` | `priceId` | What the line tells you                                                                                       |
| -------- | --------- | ------------------------------------------------------------------------------------------------------------- |
| set      | —         | It names a SKU. Join to `items` for that name and its catalog.                                                |
| null     | **set**   | A real charge nobody tagged. Tag the metric or fee, then re-apply to past invoices.                           |
| null     | **null**  | No charge sits behind this line — it's a credit purchase. Deferred revenue, so there is nothing to tag, ever. |

`priceId` is what separates a charge nobody tagged from a line that never had a
charge behind it, and the distinction matters: only the first can be fixed.

**What does your ledger call it?** Read the resolved item's
`externalReferences`. This layer only applies if you're mapping to an external
system.

| What you see | What it means                                                                 |
| ------------ | ----------------------------------------------------------------------------- |
| non-empty    | You have a code for this provider.                                            |
| empty        | No code for this provider yet. Create the external reference if you want one. |

If you're tracking how much mapping work is left, count lines whose item has no
reference for your provider, and exclude lines with a null `priceId` — those can
never be tagged, so a count that includes them never reaches zero.

<Warning>
  One combination belongs to neither list: `itemId` set with **no entry in
  `items`**. The tag points at an item that no longer resolves for you, and the
  item can't be fetched from `/v0/items` either. This is a data-integrity fault,
  not an unmapped line — don't count it and don't try to map it. Raise it.
</Warning>

<Note>
  **Re-tagging a charge takes effect on everything that hasn't been billed yet** —
  including the period currently running. You don't have to do anything else, and
  there's no window to wait for.

  Lines are generated ahead of time, so at any moment your next bill or two already
  exists. Those lines don't record an item of their own: they report whatever their
  charge is tagged with, right up until their invoice closes. So a re-tag changes
  what all of them say at once.

  **Invoices that have closed never move.** The item is written onto the line at
  close, and from then on it's a fact about that period — a later re-tag cannot
  change what a closed month exported under. If a line closed while its charge
  carried no tag, tagging the charge afterwards won't fill it in either; that is
  what re-applying a tag to past invoices is for, and it only ever fills blanks.

  **One exception: manual line items.** A manual line has no charge behind it — you
  gave it an item when you created it — so there's nothing for a re-tag to reach.
  It keeps the item you set, on every bill, and changing it means editing the line.
</Note>

#### Filling past invoice lines over the API

Two operations, deliberately separate so that reading the count can never modify
invoice data.

<Note>
  These two are internal to the Paygentic dashboard and are not part of the public
  API. Use **Apply to past invoices** on the product's Items tab instead.
</Note>

```http theme={null}
GET /v2/invoices/lineItems/itemTags/gap

{
  "object": "itemTagGap",
  "lines": 128,
  "invoices": 14,
  "issuedLines": 43,
  "issuedInvoices": 9
}
```

`lines` counts only what a fill would touch: the line has a charge behind it, that
charge is already tagged, and the line records no item yet. Lines with no charge
behind them are never counted, so the figure can actually reach zero.
`issuedLines` and `issuedInvoices` are the ones to show a user before asking them
to confirm — an invoice counts as issued once it has been issued at all, whatever
its status is now, because it may already have been exported.

```http theme={null}
PATCH /v2/invoices/lineItems/itemTags

{
  "object": "itemTagFill",
  "filled": 128,
  "remaining": { "object": "itemTagGap", "lines": 0, "invoices": 0, "issuedLines": 0, "issuedInvoices": 0 }
}
```

This is the one operation in the chain that modifies already-issued invoices. It
fills absent tags only — a line that already records an item is never changed, and
no monetary field moves. `PATCH` rather than `POST` because it is convergent: a
second call fills only what the first did not, so repeating it settles.

Show `filled`, not the figure you previewed: the gap can move between the two
calls. `remaining` is measured after the write in the same request, so a partial
fill reports a smaller remainder rather than reading as complete.

### One canonical path

A line's external identity is always resolved **line → item → references**.
Codes are never stamped onto lines directly. Keep it that way: a future
connector should read through this path rather than record its own copy, or
the same line ends up with two answers.

### What this doesn't cover

**Refunds aren't attributed per item.** A refund creates an invoice-level
credit note with no line breakdown, so the credit can't be allocated to a
product. Net-revenue-by-item is accurate until an invoice is refunded.

**There is no date filter on invoice reads.** Each read resolves its lines to
codes in one pass, but neither `GET /v2/invoices` nor
`GET /v2/invoices/lineItems` filters by period. A monthly export pages the
invoice list, filters client-side, and pulls lines per invoice.

## Archiving items

Archiving retires an item from your catalog so it can no longer be attached
to new pricing, while keeping it readable so historical invoices and line
items still resolve to it. **Archiving is the only way to retire an item** —
there is no delete operation, because a deleted item would leave the invoices
that already reference it pointing at something you can no longer read.

|                                                | Archived            |
| ---------------------------------------------- | ------------------- |
| Visible in default list results                | No                  |
| Readable directly by ID                        | Yes                 |
| Can be tagged by new metrics/fees              | No                  |
| Referenced by existing invoices and line items | Yes, still resolves |
| Restorable                                     | Yes                 |

### Archiving frees the item's external code

An external code has one canonical owner per provider. Archiving an item
releases its claim, so a replacement item can take the code over:

```text theme={null}
SFPROD_1234  ──┬── Widget A v2   owner   ← claims it after Widget A is archived
               └── Widget A      former  ← still explains last quarter's invoices
```

The archived item keeps the code as a non-primary association rather than
losing it, so its own invoices continue to export under it. Restoring the item
does **not** give the code back — by then another item may own it. Reattach it
explicitly if you need to.

### Move the charges off the item first

Because archiving hands the code to a replacement, you have to move the charges
before you archive — not after. An item with a live billable metric or fee still
tagged to it cannot be archived: the request returns `400` with
`ITEM_HAS_ANCHORED_ROWS`, and nothing changes.

That ordering exists because line generation resolves a line's item through its
charge. If the item could be archived with a charge still tagged to it, new lines
would keep landing on the retired item while the replacement owned the code and
received none of them.

The check counts charges — billable metrics and fees — because those are the only
way an item reaches a generated line. A **manual** line tagged with the item does
not block archiving, and keeps the item you gave it, so a manual line you've
already created can still bill under the retired item. Edit the line if that's
not what you want.

So a handover runs in this order:

```text theme={null}
1. Create the replacement item          POST /v0/items { "name": "Widget A v2", "catalogId": "…" }
2. Re-tag each metric/fee onto it       PATCH /v0/billableMetrics/{id} { "itemId": "<v2>" }
                                        (every unbilled line follows immediately — see the note above)
   ...or retire the charge instead      DELETE /v0/billableMetrics/{id}
                                        (a retired charge resolves to no item; its tag is left in
                                         place, so un-retiring it restores what it was tagged with)
3. Archive the old item                 PATCH /v0/items/{id} { "archived": true }
4. Give v2 the code                     POST /v0/externalReferences
                                          { "entityType": "item", "entityId": "<v2>",
                                            "provider": "salesforce", "externalId": "SFPROD_1234",
                                            "isPrimary": true }
```

Step 4 is a `POST`, not a `PATCH`: nothing before it created a reference for v2, so
there is no row to update. `PATCH /v0/externalReferences/{id}` moves an existing
reference's flags and takes no `entityId`, so it cannot be used to attach a code to
a different item.

<Warning>
  **Archiving blocks new attachments. It does not stop existing ones.**

  A charge that is already tagged with an item keeps billing, and keeps recording
  that item on every invoice line it generates. Applying tags to past invoices does
  the same. Only a *new* attachment is refused — tagging another charge with it, or
  adding it to a manual line item, returns `ITEM_ARCHIVED`.

  So archiving an item is not a way to stop it appearing on invoices. Retire the
  charges that use it for that. The rule is one sentence: **archived refuses new
  attachment decisions and never blocks a replay of an existing one.** The dashboard
  tells you how many charges are still attached before you confirm.
</Warning>

To archive an item, set `archived: true` on an update:

```http theme={null}
PATCH /v0/items/{id}
{
  "archived": true
}
```

The response reflects the retirement timestamp:

```json theme={null}
{
  "id": "itm_abc123",
  "object": "item",
  "archivedAt": "2026-07-25T00:00:00.000Z",
  "...": "..."
}
```

To restore an archived item, set `archived: false`.

By default, a `GET /v0/items` list omits archived items. Pass
`includeArchived=true` to include them:

```bash theme={null}
curl "https://api.paygentic.io/v0/items?merchantId=org_abc123&includeArchived=true"
```

Resolution queries are the exception: a lookup by `provider` and `externalId`
always returns its match, archived or not, so an external code never silently
resolves to nothing.
