Skip to main content
This how-to explains how an integration synchronises Tag Groups in Pleo with the selected Accounting Dimensions from the Accounting System. Syncing Tag Groups is the second step in the Tags Sync workflow. Tag Groups in Pleo correspond to Dimensions in the Accounting System. This step ensures the Tag Group structure in Pleo mirrors the current state of the selected Dimensions. Your integration must:
  • Retrieve selected Dimensions from the AS
  • Retrieve all Tag Groups (active and archived) from Pleo
  • Create, unarchive, update, or archive Tag Groups as needed
  • Deselect Dimensions that have become inactive in the AS

Prerequisites

Before you begin:

Scenario

This how-to continues from How to Select Dimensions for Tags Sync. The final Dimension selection from that step (Department, Project, Finance, and Cost Centre) is the input for this sync. The table below shows the starting state in both systems when this sync runs.
Dimension (AS)SelectedTag Group (Pleo)Pleo Status
Department✓ YesDepartmentActive
Project✓ YesProjectActive
Finance✓ YesDoes not exist
Cost Centre✓ YesCost CentreArchived
RegionActive

Steps

1. Retrieve Selected Dimensions from the Accounting System

Fetch all Dimensions from the AS that are currently selected in your integration’s configuration. For each Dimension, verify it is still active. Deselect any that are no longer active. Example Pseudo:
selectedDimensions = loadSelectedDimensionsFromConfig()

activeDimensions = fetchDimensionsFromAS()

for dimension in selectedDimensions:
    if dimension not in activeDimensions:
        deselect(dimension)

selectedDimensions = loadSelectedDimensionsFromConfig()

Example Result

All four selected Dimensions are still active in the AS. None are deselected.
DimensionAS StatusRetained?
DepartmentActive✓ Yes
ProjectActive✓ Yes
FinanceActive✓ Yes
Cost CentreActive✓ Yes

2. Retrieve All Tag Groups from Pleo

API Endpoint: GET /v0/tag-groups Example parameters:
  • companyId: 12abc3d4-e567-890e-1234-abc56e78fabc
  • includeArchived: true
Fetch both active and archived Tag Groups from Pleo. Including archived Tag Groups allows unarchiving rather than creating duplicates. Example Pseudo:
tagGroups = fetchTagGroupsFromPleo(includeArchived: true)

Example Request

curl -X GET "https://external.staging.pleo.io/v0/tag-groups?company_id=12abc3d4-e567-890e-1234-abc56e78fabc&include_archived=true" \
  -H "Authorization: Bearer <access_token>"

Example Response

{
  "data": [
    {
      "id": "faf59f4a-073e-4f23-9725-944d993e6310",
      "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
      "name": "Cost Centre",
      "code": "Cost Centre",
      "archived": true,
      "createdAt": "2026-05-21T13:23:03.729251Z",
      "updatedAt": "2026-05-21T13:23:14.461704Z",
      "metadata": {}
    },
    {
      "id": "768fb809-b282-4411-875a-406f8e4c5bdb",
      "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
      "name": "Department",
      "code": "Department",
      "archived": false,
      "createdAt": "2026-05-21T13:19:05.124301Z",
      "updatedAt": "2026-05-21T13:19:05.124301Z",
      "metadata": {}
    },
    {
      "id": "a9a0a2f5-a97b-41f9-aa28-dc3cde85f691",
      "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
      "name": "Project",
      "code": "Project",
      "archived": false,
      "createdAt": "2026-05-21T13:19:43.112716Z",
      "updatedAt": "2026-05-21T13:19:43.112716Z",
      "metadata": {}
    },
    {
      "id": "f1cd0c96-32c5-4b85-8b93-8f6543bc6378",
      "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
      "name": "Region",
      "code": "Region",
      "archived": false,
      "createdAt": "2026-05-21T14:12:44.543271Z",
      "updatedAt": "2026-05-21T14:12:44.543271Z",
      "metadata": {}
    }
  ]
}

What it looks like in Pleo Web App

Active Tag Groups in Pleo

Archived Tag Groups in Pleo

Example Result

Tag GroupPleo Status
DepartmentActive
ProjectActive
RegionActive
Cost CentreArchived

3. Match Dimensions to Tag Groups by Code

For each selected Dimension, attempt to find a matching Tag Group in Pleo using code (case-insensitive). Example Pseudo:
tagGroupsByCode = index tagGroups by code.toLowerCase()

for dimension in selectedDimensions:
    matchedTagGroup = tagGroupsByCode[dimension.code.toLowerCase()]

    if matchedTagGroup is null:
        createTagGroup(dimension)
    else if matchedTagGroup.archived == true:
        unarchiveTagGroup(matchedTagGroup, dimension)
    else:
        updateTagGroupIfNameChanged(matchedTagGroup, dimension)

Example Result

AS DimensionTag Group (Pleo)Pleo StatusAction
DepartmentDepartmentActiveNo action
ProjectProjectActiveNo action
FinanceDoes not exist→ Step 4: Create
Cost CentreCost CentreArchived→ Step 5: Unarchive
RegionActive→ Step 6: Archive

4. Create Tag Groups for New Dimensions

API Endpoint: POST /v0/tag-groups If an AS Dimension is selected but no matching Pleo Tag Group exists, create a new Tag Group. This only applies when no Tag Group with this code exists in Pleo (neither active nor archived). If a matching archived Tag Group exists, it is handled by step 5 (unarchive) instead. Example Pseudo:
if matchedTagGroup is null:
    newTagGroup.name     = dimension.name
    newTagGroup.code     = dimension.code
    newTagGroup.archived = false
    POST newTagGroup to Pleo

Example Request

curl -X POST "https://external.staging.pleo.io/v0/tag-groups?company_id=12abc3d4-e567-890e-1234-abc56e78fabc" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Finance",
        "code": "FIN",
        "archived": false
      }'

Example Response

{
  "data": {
    "id": "18741f60-1ded-4467-97e8-4b0e1178b3f2",
    "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
    "name": "Finance",
    "code": "FIN",
    "archived": false,
    "createdAt": "2026-05-21T13:52:03.646775474Z",
    "updatedAt": "2026-05-21T13:52:03.646775474Z",
    "metadata": {}
  }
}

What it looks like in Pleo Web App

New Finance Tag Group Created in Pleo

Example Response BAD_REQUEST

You’ll get an error similar to below if you try to create more than 5 active Tag Groups in Pleo.
{
  "error": {
    "type": "BAD_REQUEST",
    "message": "Active tag groups are limited to 5/company"
  }
}

5. Unarchive or Update Existing Tag Groups

API Endpoint: PUT /v0/tag-groups/{groupId} Example parameters: groupId: faf59f4a-073e-4f23-9725-944d993e6310 If a matching Tag Group is found:
  • AS Dimension is active, Pleo Tag Group is archived → Unarchive the Tag Group by setting archived: false
  • AS Dimension is active, Pleo Tag Group name differs → Update the Tag Group name to match the AS
  • AS Dimension is active, Pleo Tag Group name matches → No action required
Example Pseudo:
if matchedTagGroup.archived == true:
    matchedTagGroup.archived = false
    matchedTagGroup.name     = dimension.name
    PUT matchedTagGroup to Pleo

else if matchedTagGroup.name != dimension.name:
    matchedTagGroup.name = dimension.name
    PUT matchedTagGroup to Pleo

Example Request

In the below example the “Cost Centre” Tag Group is unarchived.
curl -X PUT "https://external.staging.pleo.io/v0/tag-groups/faf59f4a-073e-4f23-9725-944d993e6310" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
        "code": "Cost Centre",
        "name": "Cost Centre",
        "archived": false,
        "metadata": {}
      }'

Example Response

{
  "data": {
    "id": "faf59f4a-073e-4f23-9725-944d993e6310",
    "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
    "name": "Cost Centre",
    "code": "Cost Centre",
    "archived": false,
    "createdAt": "2026-05-21T13:23:03.729251Z",
    "updatedAt": "2026-05-21T13:59:14.102020Z",
    "metadata": {}
  }
}

What it looks like in Pleo Web App

Active Tag Groups in Pleo

Archived Tag Groups in Pleo

6. Archive Tag Groups with No Matching Selected Dimension

API Endpoint: PUT /v0/tag-groups/{groupId} If a Pleo Tag Group is active but its corresponding AS Dimension is no longer selected, archive the Tag Group. Do not delete Tag Groups. Archiving is non-destructive and reversible. Example Pseudo:
selectedCodes = selectedDimensions.map(d => d.code.toLowerCase())

for tagGroup in tagGroups where archived == false:
    if tagGroup.code.toLowerCase() not in selectedCodes:
        tagGroup.archived = true
        PUT tagGroup to Pleo

Example Request

curl -X PUT "https://external.staging.pleo.io/v0/tag-groups/f1cd0c96-32c5-4b85-8b93-8f6543bc6378" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "Region",
    "name": "Region",
    "archived": true,
    "metadata": {}
  }'

Example Response

{
  "data": {
    "id": "f1cd0c96-32c5-4b85-8b93-8f6543bc6378",
    "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
    "name": "Region",
    "code": "Region",
    "archived": true,
    "createdAt": "2026-05-21T14:12:44.543271Z",
    "updatedAt": "2026-05-21T14:25:14.216836Z",
    "metadata": {}
  }
}

Result

The table below recaps what happened to each Tag Group across all steps.
Tag GroupStep 1 (AS Selection)Step 2 (Pleo State)Step 3 (Match)Action
DepartmentSelected, activeActiveMatched (active)No action
ProjectSelected, activeActiveMatched (active)No action
FinanceSelected, activeDoes not existNo matchCreated (Step 4)
Cost CentreSelected, activeArchivedMatched (archived)Unarchived (Step 5)
RegionActiveNo matchArchived (Step 6)
Pleo now reflects the selected Dimensions from the AS.
Tag GroupFinal State in PleoAS SelectionAligned with AS?
DepartmentActiveSelected✓ Yes
ProjectActiveSelected✓ Yes
FinanceActiveSelected✓ Yes
Cost CentreActiveSelected✓ Yes
RegionArchived✓ Yes
The Tag Group structure is now ready for Tag synchronisation.

What Comes Next?


this how-to is part of: