---
title: "Contracting &amp; Hierarchies API: Quick Start Guide"
description: "This guide walks through a working read integration against the Contracting & Hierarchies API v2 — from your first call to a running incremental sync, including reconstructing your distribution hierarchy and reading it as of a past date."
url: "https://developer.agentsync.io/contracting-api-quick-start-guide"
image: "https://developer.agentsync.io/_og/d/c_Ocean.takumi,title_~Q29udHJhY3RpbmcgJiBIaWVyYXJjaGllcyBBUEk6IFF1aWNrIFN0YXJ0IEd1aWRl,description_~VGhpcyBndWlkZSB3YWxrcyB0aHJvdWdoIGEgd29ya2luZyByZWFkIGludGVncmF0aW9uIGFnYWluc3QgdGhlIENvbnRyYWN0aW5nICYgSGllcmFyY2hpZXMgQVBJIHYyIOKAlCBmcm9tIHlvdXIgZmlyc3QgY2FsbCB0byBhIHJ1bm5pbmcgaW5jcmVtZW50YWwgc3luYywgaW5jbHVkaW5nIHJlY29uc3RydWN0aW5nIHlvdXIgZGlzdHJpYnV0aW9uIGhpZXJhcmNoeSBhbmQgcmVhZGluZyBpdCBhcyBvZiBhIHBhc3QgZGF0ZS4,props_eyJ0aGVtZSI6eyJtb2RlIjoibGlnaHQiLCJjb2xvcnMiOnsicHJpbWFyeSI6IiMxODdFRkYifX19,p_Ii9jb250cmFjdGluZy1hcGktcXVpY2stc3RhcnQtZ3VpZGUi,s_ItQqFrYmYoSREQNw.png"
---

# Contracting & Hierarchies API: Quick Start Guide

This guide walks through a working read integration against the Contracting & Hierarchies API v2 — from your first call to a running incremental sync, including reconstructing your distribution hierarchy and reading it as of a past date.

The v2 surface is **read-only**. Every endpoint is a `GET`. Contracting records are created and maintained in the AgentSync application; your integration consumes them.

## [Before You Start](#before-you-start)

-   **Sandbox credentials** — email [support@agentsync.io](mailto:support@agentsync.io) to request access with these scopes:
    -   `contracting.contractassignments.read` — contract assignments, assigned carriers/products/commission levels, available uplines, **and hierarchies**
    -   `contracting.producers.read` — producers
    -   `contracting.contracts.read` — contracts
    -   `identity.profiles.read` — only if you need producer contact details (see [Step 5](#step-5-resolve-producer-contact-details))
-   **An access token** — see the [Authentication guide](https://developer.agentsync.io/api-authentication)
-   **Your AgentSync organization ID** (UUID) — required for the hierarchy endpoint. See [Step 4](#step-4-read-your-hierarchy); your AgentSync account team can also provide it.
-   **Sandbox test data** — your sandbox starts empty, and because v2 is read-only you cannot create Contracting data through the API. Request sandbox **application** access at the same time as your credentials so someone on your team can build a test hierarchy. See [Environments](https://developer.agentsync.io/api-environments#sandbox-test-data-for-contracting-hierarchies--identity).
-   **Python 3.8+** with `requests` if you're following the Python examples: `pip install requests`

All requests — contracting and hierarchies alike — use a `/contracting` path prefix:

| Environment | Base URL                                     |
| :---------- | :------------------------------------------- |
| Sandbox     | https://api.sandbox.agentsync.io/contracting |
| Production  | https://api.agentsync.io/contracting         |

Examples below assume a valid `ACCESS_TOKEN`.

> **Hierarchy data is derived from contract assignments.** If your account has no carriers, products, or contract assignments yet, the hierarchy call correctly returns an empty page rather than an error.

## [What You'll Build](#what-youll-build)

1.  Confirm connectivity and read your account configuration
2.  Read your producers
3.  Read contracts and contract assignments
4.  Read your distribution hierarchy, reconstruct the tree, and pull historical snapshots
5.  Resolve producer contact details from the Identity API
6.  Keep it all current with incremental sync and webhooks

## [Step 1: Make Your First Call](#step-1-make-your-first-call)

Fetch the carriers assigned to your account. This confirms connectivity, scopes, and base URL in one call.

**curl**

```bash
curl -X GET \
  "https://api.sandbox.agentsync.io/contracting/v2/assigned-carriers" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**Python**

```python
import requests

base_url = "https://api.sandbox.agentsync.io/contracting"
headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}

response = requests.get(f"{base_url}/v2/assigned-carriers", headers=headers)
response.raise_for_status()
data = response.json()
print(f"Fetched {data['page']['size']} carrier(s)")
```

**Example response**

```json
{
  "items": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Acme Insurance Company",
      "naic": "12345",
      "statusName": "Active",
      "createdAt": "2026-03-18T18:19:20Z",
      "updatedAt": "2026-03-18T18:19:20Z"
    }
  ],
  "page": { "size": 1, "nextToken": null }
}
```

Every v2 list response uses this envelope: results in `items`, paging metadata in `page`.

> **Got `{"items": []}`?** That's a valid response from an empty sandbox, not a failure. See [Before You Start](#before-you-start).
> 
> **Got a `404`?** Check the `/contracting` prefix. **Got a `403`?** Your token is missing a scope.

`GET /v2/assigned-products` and `GET /v2/assigned-commission-levels` complete the picture of your account configuration.

## [Step 2: Read Your Producers](#step-2-read-your-producers)

```bash
curl -X GET \
  "https://api.sandbox.agentsync.io/contracting/v2/producers?page_size=50" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```json
{
  "items": [
    {
      "type": "AGENT",
      "personId": "990e8400-e29b-41d4-a716-446655440004",
      "firmId": null,
      "npn": "1234567",
      "email": "jordan.rivera@example.com",
      "createdAt": "2026-03-18T18:19:20Z",
      "updatedAt": "2026-03-18T18:19:20Z"
    }
  ],
  "page": { "size": 1, "nextToken": null }
}
```

A producer is one entity discriminated by `type`. An `AGENT` carries `personId` with `firmId: null`; a `FIRM` is the reverse. Hold on to these — they're Identity identifiers, used in Step 5.

`GET /v2/producers/{producerId}` returns the detail view, which additionally embeds the producer's bank accounts with account and routing numbers masked to `****{last4}`.

## [Step 3: Read Contracts and Assignments](#step-3-read-contracts-and-assignments)

Contracts represent the relationship between your organization and a carrier. Contract assignments place a producer against a product under that contract.

```bash
curl -X GET \
  "https://api.sandbox.agentsync.io/contracting/v2/contract-assignments?page_size=50" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Assignments inline the names you'd otherwise have to look up — product, commission level, and status all arrive with both id and name, so no second call is needed to render a row.

Related endpoints:

-   `GET /v2/contracts`, `GET /v2/contracts/{id}`
-   `GET /v2/contract-assignments/{id}`
-   `GET /v2/contract-assignment-changes` — point-in-time amendments, each carrying its own status and snapshot window

## [Step 4: Read Your Hierarchy](#step-4-read-your-hierarchy)

The hierarchy endpoint is scoped by organization ID — the UUID identifying your agency or carrier in AgentSync. If you don't have it, the `assigned-carriers` response from Step 1 carries your organization context, and your AgentSync account team can confirm it.

```bash
curl -X GET \
  "https://api.sandbox.agentsync.io/contracting/v2/organizations/$ORG_ID/hierarchies" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**Example response**

```json
{
  "items": [
    {
      "contractAssignmentId": "ca-uuid-imo",
      "uplineContractAssignmentId": null,
      "producerName": "Acme IMO",
      "type": "FIRM",
      "personId": null,
      "firmId": "770e8400-e29b-41d4-a716-446655440002",
      "npn": "1234567",
      "productId": "prod-uuid-2222",
      "productName": "Term Life 10-Year",
      "assignmentStatusName": "Approved",
      "commissionLevelName": "L1",
      "commissionLevel": 100.00,
      "stateAbbreviations": ["CA", "TX", "NY"],
      "writingNumber": null,
      "effectiveOn": "2026-01-15",
      "expiresOn": null,
      "active": true
    },
    {
      "contractAssignmentId": "ca-uuid-agency",
      "uplineContractAssignmentId": "ca-uuid-imo",
      "producerName": "Westside Agency",
      "type": "FIRM",
      "personId": null,
      "firmId": "770e8400-e29b-41d4-a716-446655440003",
      "npn": "2345678",
      "productId": "prod-uuid-2222",
      "productName": "Term Life 10-Year",
      "assignmentStatusName": "Approved",
      "commissionLevelName": "L2",
      "commissionLevel": 90.00,
      "stateAbbreviations": ["CA"],
      "writingNumber": null,
      "effectiveOn": "2026-02-01",
      "expiresOn": null,
      "active": true
    },
    {
      "contractAssignmentId": "ca-uuid-producer",
      "uplineContractAssignmentId": "ca-uuid-agency",
      "producerName": "Joe Producer",
      "type": "AGENT",
      "personId": "990e8400-e29b-41d4-a716-446655440004",
      "firmId": null,
      "npn": "15645555",
      "productId": "prod-uuid-2222",
      "productName": "Term Life 10-Year",
      "assignmentStatusName": "Approved",
      "commissionLevelName": "L3",
      "commissionLevel": 80.00,
      "stateAbbreviations": ["CA"],
      "writingNumber": "WN-15645555",
      "effectiveOn": "2026-03-01",
      "expiresOn": null,
      "active": true
    }
  ],
  "page": { "size": 3, "nextToken": null }
}
```

**Reading the flat list:**

-   **Root nodes:** `uplineContractAssignmentId == null`
-   **Direct upline:** follow `uplineContractAssignmentId` to the node whose `contractAssignmentId` matches
-   **Inactive positions:** `active: false` — a node is inactive when its own status is inactive or any node above it is
-   **Nodes span all products:** the list covers every product in your hierarchy; group or filter by `productId` for per-product views

Fetch every page before reconstructing — a child can appear on a later page than its parent. For the complete field-by-field schema, see [Hierarchy Nodes](https://developer.agentsync.io/contracting-api-overview#hierarchy-nodes).

### [Reconstruct the Tree](#reconstruct-the-tree)

Group nodes by `uplineContractAssignmentId` and attach children to their parents:

```python
nodes = list(client.hierarchy(ORG_ID))          # see the reference implementation below
roots, children = build_tree(nodes)

def walk(node, depth=0):
    print(f"{'  ' * depth}{node['producerName']} — {node['commissionLevelName']}")
    for child in children.get(node["contractAssignmentId"], []):
        walk(child, depth + 1)

for root in roots:
    walk(root)
```

Against the response above, that prints:

```text
Acme IMO — L1
  Westside Agency — L2
    Joe Producer — L3
```

### [Get a Point-in-Time Snapshot](#get-a-point-in-time-snapshot)

Pass `as_of` (`yyyy-MM-dd`) to see the hierarchy as it existed on a given date:

```bash
curl -X GET \
  "https://api.sandbox.agentsync.io/contracting/v2/organizations/$ORG_ID/hierarchies?as_of=2026-01-15" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

This is the mechanism for commissions reconciliation — query the hierarchy as of the transaction date to confirm who the upline was at that moment.

> **No `as_of` → current state.** Always supply an explicit `as_of` when querying for historical purposes. The current state may have changed since the transaction.

## [Step 5: Resolve Producer Contact Details](#step-5-resolve-producer-contact-details)

Contracting holds placement — where a producer sits. Addresses, phones, bank accounts, and E&O policies live in the [Identity API](https://developer.agentsync.io/identity-api-overview).

The two join directly: the `personId` and `firmId` from Step 2 **are** Identity identifiers. No mapping, no NPN lookup.

```bash
# personId came straight from the Contracting producer
curl -X GET \
  "https://api.sandbox.agentsync.io/identity/v2/persons/$PERSON_ID/addresses" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Note the different path prefix (`/identity`) and the separate scope (`identity.profiles.read`). See [Joining with the Contracting API](https://developer.agentsync.io/identity-api-overview#joining-with-the-contracting-api).

## [Step 6: Keep Downstream Systems in Sync](#step-6-keep-downstream-systems-in-sync)

Use `updated_since` to fetch only what changed:

```bash
curl -X GET \
  "https://api.sandbox.agentsync.io/contracting/v2/contract-assignments?updated_since=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

-   Takes an RFC3339 UTC timestamp and returns records modified at or after it, inclusive. Store your last sync time as a high-water mark and overlap the window slightly.
-   Available on `GET /v2/producers`, `/v2/contracts`, `/v2/contract-assignments`, `/v2/contract-assignment-changes`, `/v2/assigned-carriers`, `/v2/assigned-products`, and `/v2/assigned-commission-levels`.
-   Pair it with [webhook events](https://developer.agentsync.io/contracting-api-webhooks) for near-real-time updates, and keep `updated_since` polling as the reconciliation safety net.

The hierarchy endpoint has no `updated_since` — hierarchy changes arrive as webhook events instead:

| Event                                      | When It Fires                                                                  |
| :----------------------------------------- | :----------------------------------------------------------------------------- |
| hierarchy.producer.added                   | A producer is newly placed under an upline                                     |
| hierarchy.producer.changed                 | A producer's upline and/or commission level changes (changedFields says which) |
| hierarchy.producer.relationship.terminated | A producer's relationship ends — the assignment enters a terminal status       |

Event payloads carry the same `contractAssignmentId` used as the node key in Step 4, so you can update your local tree directly from the event. See [Contracting API Webhook Events](https://developer.agentsync.io/contracting-api-webhooks) for payload schemas and the [Webhooks Quick Start Guide](https://developer.agentsync.io/webhooks-quick-start-guide) to register your endpoint.

> **Detecting firms in events: test `firmId` for presence, not `personId` for null.** On the REST hierarchy node a `FIRM` position has `personId: null`, but in `hierarchy.producer.*` events `personId` is always populated. Logic branching on `personId == null` works against REST and then silently classifies every firm as an individual when pointed at the event stream.

## [Recommended Workflows](#recommended-workflows)

### [Commissions: Upline Chain at Transaction Time](#commissions-upline-chain-at-transaction-time)

```text
1. GET /v2/organizations/{orgId}/hierarchies?as_of={transactionDate}
2. Find the producer's node (match on your stored contractAssignmentId)
3. Walk uplineContractAssignmentId links for the full chain
4. Read commissionLevelName / commissionLevel from each node in the chain
```

### [Real-Time Hierarchy Sync](#real-time-hierarchy-sync)

```text
1. Initial load: fetch all nodes (Step 4) and store keyed on contractAssignmentId
2. Subscribe to hierarchy.producer.* events
3. On added:      insert the node under uplineContractAssignmentId
   On changed:    update the fields listed in changedFields
   On terminated: mark the node (and its subtree) inactive
4. Periodically reconcile with a full as_of fetch as a safety net
```

### [Full Hierarchy Reconciliation](#full-hierarchy-reconciliation)

```text
1. GET /v2/organizations/{orgId}/hierarchies?as_of={reconciliationDate}
2. Match each node to your local records on contractAssignmentId
3. In AgentSync but not local  → new positions to add
   Local but not in AgentSync  → terminated or restructured
   In both                     → compare upline, commission level, status
```

### [Window Comparison](#window-comparison)

```text
The API returns point-in-time snapshots — there is no date-range filter.
To find changes within a window:

1. GET .../hierarchies?as_of={windowStart}
2. GET .../hierarchies?as_of={windowEnd}
3. Diff the two sets on contractAssignmentId
```

## [Pagination](#pagination)

List responses are token-paginated. When `page.nextToken` is non-null, pass it back as the `page_token` query parameter:

```bash
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://api.sandbox.agentsync.io/contracting/v2/producers?page_token=NEXT_TOKEN"
```

> **The parameter is `page_token`, not `nextToken`.** The response field and the query parameter are spelled differently, and unrecognized parameters are ignored rather than rejected — so the wrong name silently returns the first page again instead of an error.

`page_size` defaults to 25 and must be `1–250`; anything outside that range is rejected with a `400` rather than clamped. See [Pagination](https://developer.agentsync.io/api-pagination) for the full pattern.

## [Error Handling](#error-handling)

| Status | Likely cause                                                                     | Fix                                                                              |
| :----- | :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- |
| 400    | Invalid page_size, malformed page_token, or bad as_of/updated_since format       | Check the parameter format; page_size must be 1–250                              |
| 401    | Token expired                                                                    | Re-authenticate and retry once                                                   |
| 403    | Token is missing a required scope                                                | Check the scope for that endpoint in Authentication                              |
| 404    | Wrong path prefix, an organization ID not associated with your credentials, or a resource belonging to another account | Confirm the /contracting prefix and your organization ID; cross-account resources return 404, never 403 |
| 429    | Rate limit exceeded                                                              | Back off exponentially — see Rate Limits                                         |

v2 errors return a compact object:

```json
{
  "message": "page_size must be between 1 and 250 but was 5000",
  "details": []
}
```

Capture the `DD-Trace-ID` response header on failures and include it in support tickets — see [Traceability](https://developer.agentsync.io/api-traceability).

## [Token Management](#token-management)

Tokens are valid for 60 minutes — reuse them across requests rather than fetching one per call, which is the most common way integrations hit the token endpoint's rate limit. See [Authentication: Token Expiration & Reuse](https://developer.agentsync.io/api-authentication#token-expiration--reuse) for a reusable client.

## [Complete Python Reference Implementation](#complete-python-reference-implementation)

A read integration that pages through producers and assignments, resolves contact details from Identity, and supports incremental runs.

```python
import requests


class ContractingClient:
    """Minimal read client for Contracting v2 + Identity v2."""

    def __init__(self, access_token, base="https://api.sandbox.agentsync.io"):
        self.contracting = f"{base}/contracting"
        self.identity = f"{base}/identity"
        self.session = requests.Session()
        self.session.headers["Authorization"] = f"Bearer {access_token}"

    def paginate(self, url, params=None):
        """Yield every item from a v2 token-paginated endpoint."""
        params = dict(params or {})
        while True:
            response = self.session.get(url, params=params)
            response.raise_for_status()
            data = response.json()

            yield from data["items"]

            token = data["page"]["nextToken"]
            if token is None:
                return
            params["page_token"] = token

    def producers(self, updated_since=None):
        params = {"page_size": 250}
        if updated_since:
            params["updated_since"] = updated_since
        return self.paginate(f"{self.contracting}/v2/producers", params)

    def hierarchy(self, organization_id, as_of=None):
        params = {"as_of": as_of} if as_of else {}
        return self.paginate(
            f"{self.contracting}/v2/organizations/{organization_id}/hierarchies", params
        )

    def addresses_for(self, producer):
        """Resolve a Contracting producer's addresses from the Identity API.

        personId/firmId on a producer are Identity identifiers — no mapping needed.
        """
        if producer["type"] == "FIRM":
            path = f"{self.identity}/v2/firms/{producer['firmId']}/addresses"
        else:
            path = f"{self.identity}/v2/persons/{producer['personId']}/addresses"
        return list(self.paginate(path))


def build_tree(nodes):
    """Rebuild the hierarchy from the flat node list."""
    children = {}
    roots = []
    for node in nodes:
        upline = node["uplineContractAssignmentId"]
        if upline is None:
            roots.append(node)
        else:
            children.setdefault(upline, []).append(node)
    return roots, children


if __name__ == "__main__":
    client = ContractingClient(access_token="YOUR_ACCESS_TOKEN")

    # Full producer roster, with contact details resolved from Identity.
    for producer in client.producers():
        addresses = client.addresses_for(producer)
        print(producer["npn"], producer["type"], len(addresses), "address(es)")

    # Hierarchy, fetched completely before rebuilding the tree.
    nodes = list(client.hierarchy("YOUR_ORG_ID"))
    roots, children = build_tree(nodes)
    print(f"{len(nodes)} node(s), {len(roots)} root(s)")
```

## [Next Steps](#next-steps)

-   [Contracting & Hierarchies API Overview](https://developer.agentsync.io/contracting-api-overview) — the full endpoint listing and hierarchy node schema
-   [Contracting API Best Practices](https://developer.agentsync.io/contracting-api-best-practices) — sync patterns and lookup caching
-   [Contracting API Webhook Events](https://developer.agentsync.io/contracting-api-webhooks) — react to assignment and hierarchy changes
-   [Identity API Quick Start](https://developer.agentsync.io/identity-api-quick-start-guide) — producer contact, bank account, and E&O data
-   [Pagination](https://developer.agentsync.io/api-pagination) · [Rate Limits](https://developer.agentsync.io/api-rate-limits) · [Traceability](https://developer.agentsync.io/api-traceability)