How to use the directory sync JSON file to import team hierarchy and membership into Span

Last updated: October 27, 2026

The Span Sync API lets you send your people records, team hierarchy, and team memberships to Span as JSON. Span checks the payload, then stages it as a directory sync that you review before anything changes.

Why it matters

Most HR systems don't hold a complete engineering team structure. The Sync API lets you send that structure from whatever system does hold it, such as an internal service or a spreadsheet export. Span recommends the API as the source of truth for people and team hierarchy where you can use it.

How it works

You POST a JSON body with up to two top-level arrays:

  • peopleHierarchy: people, nested under their managers through each person's reports array.

  • groupsHierarchy: teams, nested through each team's children array, with members in memberEmails.

Send only the sections your organization has enabled for the Sync API. Span sets these up with you: people only, teams only, or both, and whether memberships sync.

Team fields

Field

Required

Notes

externalId

Yes

Your stable ID for the team. Must be unique across the whole hierarchy.

name

Yes

Display name.

children

Yes

Array of child teams. Use [] for none.

memberEmails

No

Members' emails, each once per team. An empty array counts as membership data.

managerEmail

No

The team's manager.

slug

No

Starts with a lowercase letter, then lowercase letters and digits with single - or _ separators (never at the end), 1 to 128 characters.

Person fields

Field

Required

Notes

email

Yes

Unique across the whole hierarchy. Span trims and lowercases it.

firstName, lastName

Yes

startDate

Yes

A date such as 2026-09-15 or a timestamp such as 2026-09-15T00:00:00Z. On or after 1970-01-01.

reports

Yes

Array of direct reports. Use [] for none.

terminationDate

No

Same formats as startDate.

jobTitle, jobLevel, jobFamily, location, timezone

No

vcsUsernamesByPlatform

No

Git usernames keyed by platform: github, github_enterprise_server, gitlab, ado. For Azure DevOps, use the person's sign-in email.

Setup

Before you start

  • Ask your Span account team to configure the Sync API as a roster source for your organization.

  • The token you use, and anyone reviewing syncs in Span, needs the Manage Directory Sync permission. Owners and Admins hold it by default, and you can grant it to any permission group.

  • Create a Span API token. See How to Create a Span API Token.

Step 1: Fetch your current structure (optional)

A GET to the same endpoint returns your current people and teams in the shape POST accepts. Your organization itself is implied, so the teams you get back are its top-level teams. The response always includes both arrays, even when one is empty, so trim it to the sections your organization has enabled before you send it back.

Step 2: Build your JSON file

Create a file such as directory-sync-payload.json. This example sends two top-level teams with a nested team and memberships:

{
  "groupsHierarchy": [
    {
      "externalId": "platform",
      "name": "Platform",
      "slug": "platform",
      "managerEmail": "platform-manager@company.com",
      "memberEmails": ["alice@company.com", "bob@company.com"],
      "children": []
    },
    {
      "externalId": "product-eng",
      "name": "Product Engineering",
      "slug": "product-engineering",
      "managerEmail": "product-manager@company.com",
      "memberEmails": ["carol@company.com", "dan@company.com"],
      "children": [
        {
          "externalId": "growth",
          "name": "Growth",
          "slug": "growth",
          "managerEmail": "growth-manager@company.com",
          "memberEmails": ["erin@company.com"],
          "children": []
        }
      ]
    }
  ]
}

To send people in the same payload, add peopleHierarchy. When you do, every manager and member email in groupsHierarchy must also appear in peopleHierarchy:

{
  "peopleHierarchy": [
    {
      "email": "vp-eng@company.com",
      "firstName": "Vera",
      "lastName": "Ng",
      "timezone": "America/New_York",
      "jobTitle": "VP Engineering",
      "jobFamily": "Engineering",
      "location": "New York",
      "startDate": "2024-01-15",
      "terminationDate": null,
      "vcsUsernamesByPlatform": { "github": ["verang"] },
      "reports": [
        {
          "email": "platform-manager@company.com",
          "firstName": "Priya",
          "lastName": "Shah",
          "timezone": "America/Los_Angeles",
          "jobTitle": "Director, Platform",
          "jobFamily": "Engineering",
          "location": "San Francisco",
          "startDate": "2023-05-01",
          "terminationDate": null,
          "vcsUsernamesByPlatform": { "github": ["pshah"] },
          "reports": []
        }
      ]
    }
  ],
  "groupsHierarchy": [
    {
      "externalId": "leadership",
      "name": "Leadership",
      "managerEmail": "vp-eng@company.com",
      "memberEmails": ["vp-eng@company.com"],
      "children": []
    },
    {
      "externalId": "platform",
      "name": "Platform",
      "managerEmail": "platform-manager@company.com",
      "memberEmails": ["platform-manager@company.com"],
      "children": []
    }
  ]
}

Step 3: Send the payload

The API uses bearer-token authentication:

curl -X POST "https://api.span.app/v1/directory-sync" \
  -H "Authorization: Bearer YOUR_SPAN_TOKEN" \
  -H "Content-Type: application/json" \
  --data @directory-sync-payload.json

A successful response is {"success": true}. It means Span accepted the payload. The staged sync appears on the Directory Sync page once Span has processed it, which is not immediate.

Step 4: Review and apply in Span

Open Settings → Directory Sync. Additions and removals of people, teams, and memberships are listed for review. Field-only changes, such as a new title, apply with the sync and are not listed one by one. Click Apply when it looks right. If Span has turned on auto-apply for your organization, the sync applies on its own.

Step 5: Fix errors at the source

If something in the staged sync is wrong, fix your source data or JSON and send it again. The next sync replaces the staged one.

Things to know

  • One root means your organization: if groupsHierarchy has more than one root, each root becomes a top-level team in your organization. A single root is treated as the organization itself and must match your existing root team, or the sync fails when applied. If you have exactly one top-level team, nest it under a root that matches your organization. Use the GET response to see your structure.

  • Keep externalId stable: Span recognizes a team across syncs by its externalId. Changing it makes the old team look removed and a new one look added.

  • Teams missing from a sync are removed, people are terminated: the payload is a full picture. People you leave out are terminated (Span keeps their history) unless Span has turned on deletion for your organization.

  • Git usernames: use vcsUsernamesByPlatform. The older vcsUsernames list still works only while your organization has one Git platform connected. Usernames for a platform you haven't connected are accepted and ignored.

  • Worked example: you send 40 people and two root teams, Platform and Product Engineering. One manager email on Platform isn't in peopleHierarchy, and one person appears twice. Span returns 412 with two errors and stages nothing. You fix both and resend, and Span stages a sync with 40 people and 2 top-level teams.

Troubleshooting 412 errors

Span checks the payload before staging it. Any failure returns 412 with a list of errors, and nothing is staged. Common causes:

  • Directory sync not configured: Span hasn't set up the Sync API as a source for your organization yet.

  • "People sync is enabled but data was not provided" or "... is disabled but data was provided": the sections you sent don't match the ones enabled for your organization. The same applies to groups.

  • "Memberships sync is enabled but data was not provided": no team in the payload has memberEmails. Add them, even as empty arrays.

  • Invalid or duplicate emails: each person's email must be valid and appear once in the whole hierarchy.

  • Duplicate group external ID: each externalId must be unique.

  • Manager or member "is not in the data": when you send peopleHierarchy, every managerEmail and member email must be in it. Span can turn on an option to ignore missing managers.

  • vcsUsernames can't be assigned to a platform: more than one Git platform is connected. Send vcsUsernamesByPlatform instead, and don't send both fields for one person.

Conflicts with your existing team structure aren't checked at this stage. They show up when the sync is applied, as an error on the Directory Sync page.

Related articles

Using Directory Sync for Roster Management

How do I enable automatic directory sync in Span?

How to Create a Span API Token

Need help?

Contact your Span account team or reach out to support@span.app.