Skip to main content
This how-to explains how an integration synchronises Tags within each Tag Group in Pleo with the Dimension Values from the Accounting System. Syncing Tags is the third and final step in the Tags Sync workflow. Tags in Pleo correspond to Dimension Values in the Accounting System. This step ensures bookkeepers always have accurate, current Dimension Values available when coding expenses. Your integration must:
  • Retrieve Dimension Values from the AS for each selected Dimension
  • Retrieve all Tags (active and archived) from Pleo for each Tag Group
  • Create, unarchive, update, or archive Tags as needed
  • Resolve duplicate Tags when multiple Tags share the same code

Prerequisites

Before you begin:

Scenario

This how-to continues from How to Sync Tag Groups with Accounting Dimensions. The four active Tag Groups from that step (Department, Project, Finance, and Cost Centre) are the input for this sync. This how-to uses the Department Tag Group to illustrate each step. The table below shows the starting state for the Department Tag Group before this sync runs.
Tag (Pleo)Pleo StatusDimension Value (AS)AS Status
EngineeringActiveEngineering (ENG)Active
MarketingArchivedMarketing (MKT)Active
SalesActiveDoes not exist
Operations (OPS)Active

Steps

1. Iterate Over Each Selected Tag Group

For each active Tag Group in Pleo (corresponding to a selected Dimension), perform the following steps. Example Pseudo:
activeTagGroups = fetchTagGroupsFromPleo(includeArchived: false)

for tagGroup in activeTagGroups:
    syncTagsForTagGroup(tagGroup)

Example Result

Tag GroupPleo StatusProcess Tags?
DepartmentActive✓ Yes
ProjectActive✓ Yes
FinanceActive✓ Yes
Cost CentreActive✓ Yes
The following steps use Department as the example.

2. Retrieve Dimension Values from the Accounting System

For the current Tag Group, fetch the Dimension Values from the AS for the matching Dimension. Include all values: active, archived, blocked, expired, or deactivated. The sync logic determines what to do with each. Example Pseudo:
dimensionValues = fetchDimensionValuesFromAS(dimension.code)

Example Response

The response format depends on your Accounting System. The following is a representative example for the Department Dimension:
{
  "dimensionValues": [
    { "code": "ENG", "name": "Engineering", "status": "active" },
    { "code": "MKT", "name": "Marketing",   "status": "active" },
    { "code": "OPS", "name": "Operations",  "status": "active" }
  ]
}

Example Result

Dimension ValueAS StatusInclude in Sync?
Engineering (ENG)Active✓ Yes
Marketing (MKT)Active✓ Yes
Operations (OPS)Active✓ Yes

3. Retrieve All Tags from Pleo for the Tag Group

API Endpoint: GET /v0/tag-groups/{groupId}/tags Example parameters: groupId: 768fb809-b282-4411-875a-406f8e4c5bdb, includeArchived: true Fetch both active and archived Tags for the current Tag Group. Including archived Tags allows unarchiving rather than creating duplicates. Example Pseudo:
tags = fetchTagsFromPleo(tagGroup.id, includeArchived: true)

Example Request

curl -X GET "https://external.staging.pleo.io/v0/tag-groups/768fb809-b282-4411-875a-406f8e4c5bdb/tags?include_archived=true" \
  -H "Authorization: Bearer <access_token>"

Example Response

{
  "data": [
    {
      "id": "92771fb4-cdd7-4679-b594-f21a005c731a",
      "groupId": "768fb809-b282-4411-875a-406f8e4c5bdb",
      "name": "Engineering · ENG",
      "code": "ENG",
      "archived": false,
      "createdAt": "2026-05-22T14:12:43.358326Z",
      "updatedAt": "2026-05-22T14:12:43.358326Z"
    },
    {
      "id": "8fef04fe-0fe5-44ef-b863-a6fa93fdcfc4",
      "groupId": "768fb809-b282-4411-875a-406f8e4c5bdb",
      "name": "Marketing · MKT",
      "code": "MKT",
      "archived": true,
      "createdAt": "2026-05-22T14:13:00.697506Z",
      "updatedAt": "2026-05-22T14:19:53.667246Z"
    },
    {
      "id": "7b079251-c225-46e6-9f0b-6f2e1b1cdec5",
      "groupId": "768fb809-b282-4411-875a-406f8e4c5bdb",
      "name": "Sales · Sales",
      "code": "Sales",
      "archived": false,
      "createdAt": "2026-05-22T14:13:25.178728Z",
      "updatedAt": "2026-05-22T14:13:25.178728Z"
    }
  ],
  "pagination": {
    "hasPreviousPage": false,
    "hasNextPage": false,
    "currentRequestPagination": {
      "sortingKeys": [],
      "sortingOrder": [],
      "parameters": {
        "include_archived": [
          "true"
        ],
        "groupId": [
          "768fb809-b282-4411-875a-406f8e4c5bdb"
        ]
      }
    },
    "startCursor": "~=AAAAAADKCBSFWFK3T3YA=SJ3R7NGN25DHTNMU6INAAXDTDI",
    "endCursor": "~=AAAAAADKCBSIKCVHFRAA=PMDZEUOCEVDONHYLN4XBWHG6YU",
    "total": 3
  }
}

Example Result

TagPleo Status
Engineering (ENG)Active
Marketing (MKT)Archived
Sales (Sales)Active

What it looks like in Pleo Web App

Active Tags in Pleo for Department Tag Group

Archived Tags in Pleo for Department Tag Group

4. Resolve Duplicate Tags

Before matching, detect and resolve any Tags in Pleo that share the same code. If multiple Tags share the same code:
  • Retain the Tag whose name matches the current AS Dimension Value name (case-insensitive)
  • If no name match, retain one Tag chosen at random (using the most recently created Tag as a tiebreaker is a reasonable approach)
  • Archive all other duplicates
Example Pseudo:
tagsByCode = groupTagsByCode(tags)

for code, duplicates in tagsByCode where duplicates.count > 1:
    dimensionValue = findDimensionValue(dimensionValues, code)
    tagToKeep = findBestMatch(duplicates, dimensionValue.name)
    tagsToArchive = duplicates excluding tagToKeep

    for tag in tagsToArchive:
        archiveTag(tag)

Example Result

No duplicate Tags detected for the Department Tag Group. No action required.

5. Match Dimension Values to Tags by Code

For each active Dimension Value from the AS, attempt to find a matching Tag in Pleo using code (case-insensitive). Example Pseudo:
tagsByCode = index tags by code.toLowerCase()

activeDimensionValues = filter dimensionValues where status == "active"

for dimensionValue in activeDimensionValues:
    matchedTag = tagsByCode[dimensionValue.code.toLowerCase()]

    if matchedTag is null:
        createTag(tagGroup, dimensionValue)
    else if matchedTag.archived == true:
        unarchiveTag(matchedTag, dimensionValue)
    else:
        updateTagIfNameChanged(matchedTag, dimensionValue)

Example Result

Dimension Value (AS)Tag (Pleo)Pleo StatusAction
Engineering (ENG)Engineering (ENG)ActiveNo action
Marketing (MKT)Marketing (MKT)Archived→ Step 7: Unarchive
Operations (OPS)Does not exist→ Step 6: Create
Sales (Sales)Active→ Step 8: Archive

6. Create Tags for New Dimension Values

API Endpoint: POST /v0/tag-groups/{groupId}/tags Example parameters: groupId: 768fb809-b282-4411-875a-406f8e4c5bdb If an AS Dimension Value is active but no matching Pleo Tag exists, create a new Tag within the Tag Group. Example Pseudo:
if matchedTag is null:
    newTag.name     = dimensionValue.name
    newTag.code     = dimensionValue.code
    newTag.archived = false
    POST newTag to Pleo

Example Request

curl -X POST "https://external.staging.pleo.io/v0/tag-groups/768fb809-b282-4411-875a-406f8e4c5bdb/tags" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Operations",
        "code": "OPS",
        "archived": false
      }'

Example Response

{
  "data": {
    "id": "c55b216a-ffd1-426e-8eb2-fcf8b7376a81",
    "groupId": "768fb809-b282-4411-875a-406f8e4c5bdb",
    "name": "Operations",
    "code": "OPS",
    "archived": false,
    "createdAt": "2026-05-22T14:27:56.239032674Z",
    "updatedAt": "2026-05-22T14:27:56.239032979Z"
  }
}

What it looks like in Pleo Web App

Active Tags in Pleo for Department Tag Group

7. Unarchive or Update Existing Tags

API Endpoint: PUT /v0/tags/{tagId} Example parameters: tagId: 8fef04fe-0fe5-44ef-b863-a6fa93fdcfc4 If a matching Tag is found:
  • AS Dimension Value is active, Pleo Tag is archived → Unarchive the Tag by setting archived: false and update the name if changed
  • AS Dimension Value is active, Pleo Tag name differs → Update the Tag name to match the AS
  • AS Dimension Value is active, Pleo Tag name matches → No action required
Example Pseudo:
if matchedTag.archived == true:
    matchedTag.archived = false
    matchedTag.name     = dimensionValue.name
    PUT matchedTag to Pleo

else if matchedTag.name != dimensionValue.name:
    matchedTag.name = dimensionValue.name
    PUT matchedTag to Pleo

Example Request

curl -X PUT "https://external.staging.pleo.io/v0/tags/8fef04fe-0fe5-44ef-b863-a6fa93fdcfc4" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Marketing",
        "code": "MKT",
        "archived": false
      }'

Example Response

{
  "data": {
    "id": "8fef04fe-0fe5-44ef-b863-a6fa93fdcfc4",
    "groupId": "768fb809-b282-4411-875a-406f8e4c5bdb",
    "name": "Marketing",
    "code": "MKT",
    "archived": false,
    "createdAt": "2026-05-22T14:13:00.697506Z",
    "updatedAt": "2026-05-22T14:19:53.667246Z"
  }
}

What it looks like in Pleo Web App

Active Tags in Pleo for Department Tag Group

Archived Tags in Pleo for Department Tag Group - Empty

8. Archive Tags with No Matching Active Dimension Value

API Endpoint: PUT /v0/tags/{tagId} For any active Tag that does not match an active Dimension Value (because the Dimension Value is absent, deleted, blocked, expired, or deactivated in the AS), archive it. Do not delete Tags. Archiving is non-destructive and reversible. Example Pseudo:
activeDimensionCodes = activeDimensionValues.map(dv => dv.code.toLowerCase())

for tag in tags where archived == false:
    if tag.code.toLowerCase() not in activeDimensionCodes:
        tag.archived = true
        PUT tag to Pleo

Example Request

curl -X PUT "https://external.staging.pleo.io/v0/tags/7b079251-c225-46e6-9f0b-6f2e1b1cdec5" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Sales",
        "code": "Sales",
        "archived": true
      }'

Example Response

{
  "data": {
    "id": "7b079251-c225-46e6-9f0b-6f2e1b1cdec5",
    "groupId": "768fb809-b282-4411-875a-406f8e4c5bdb",
    "name": "Sales",
    "code": "Sales",
    "archived": true,
    "createdAt": "2026-05-22T14:13:25.178728Z",
    "updatedAt": "2026-05-22T14:22:19.413192Z"
  }
}

What it looks like in Pleo Web App

Active Tags in Pleo for Department Tag Group

Archived Tags in Pleo for Department Tag Group

Result

The table below recaps what happened to each Tag across all steps for the Department Tag Group.
TagStep 2 (AS Dimension Value)Step 3 (Pleo State)Step 5 (Match)Action
EngineeringActive (ENG)ActiveMatched (active)No action
MarketingActive (MKT)ArchivedMatched (archived)Unarchived (Step 7)
OperationsActive (OPS)Does not existNo matchCreated (Step 6)
SalesActiveNo matchArchived (Step 8)
Pleo now reflects the active Dimension Values from the AS for the Department Tag Group.
TagFinal State in PleoAS Dimension ValueAligned with AS?
EngineeringActiveActive✓ Yes
MarketingActiveActive✓ Yes
OperationsActiveActive✓ Yes
SalesArchived✓ Yes
This same process runs for every active Tag Group (Project, Finance, and Cost Centre) until all Tags across all Tag Groups are aligned with the AS.

What Comes Next?


this how-to is part of: