---
title: "Identity API: Quick Start Guide"
description: "This guide takes you from credentials to a working Identity integration — reading your producer roster, resolving contact and financial details, and keeping it current."
url: "https://developer.agentsync.io/identity-api-quick-start-guide"
image: "https://developer.agentsync.io/_og/d/c_Ocean.takumi,title_~SWRlbnRpdHkgQVBJOiBRdWljayBTdGFydCBHdWlkZQ,description_~VGhpcyBndWlkZSB0YWtlcyB5b3UgZnJvbSBjcmVkZW50aWFscyB0byBhIHdvcmtpbmcgSWRlbnRpdHkgaW50ZWdyYXRpb24g4oCUIHJlYWRpbmcgeW91ciBwcm9kdWNlciByb3N0ZXIsIHJlc29sdmluZyBjb250YWN0IGFuZCBmaW5hbmNpYWwgZGV0YWlscywgYW5kIGtlZXBpbmcgaXQgY3VycmVudC4,props_eyJ0aGVtZSI6eyJtb2RlIjoibGlnaHQiLCJjb2xvcnMiOnsicHJpbWFyeSI6IiMxODdFRkYifX19,p_Ii9pZGVudGl0eS1hcGktcXVpY2stc3RhcnQtZ3VpZGUi,s_1zTJCDIgYLEX2Smv.png"
---

# Identity API: Quick Start Guide

This guide takes you from credentials to a working Identity integration — reading your producer roster, resolving contact and financial details, and keeping it current.

The Identity API is the source of record for **who a producer is**: name, NPN, addresses, phones, bank accounts, and E&O coverage. Where they sit in your distribution network lives in the [Contracting API](https://developer.agentsync.io/contracting-api-overview).

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

-   **Sandbox credentials** — email [support@agentsync.io](mailto:support@agentsync.io) with the scopes you need:
    -   `identity.profiles.read` — read persons, firms, addresses, phones, bank accounts, E&O policies
    -   `identity.profiles.write` — **only if you create or update records.** This one scope covers create, update _and delete_ across every Identity resource; there is no read-plus-create tier. Don't request it for a read-only integration.
-   **An access token** — see the [Authentication guide](https://developer.agentsync.io/api-authentication)
-   **Python 3.8+** with `requests` if you're following the Python examples: `pip install requests`

All Identity requests use an `/identity` path prefix:

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

Unlike Contracting, you **can** build sandbox test data through the API with `identity.profiles.write` — see [Step 5](#step-5-create-a-producer-optional).

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

List the persons visible to your account.

**curl**

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

**Python**

```python
import requests

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

response = requests.get(
    f"{base_url}/v2/persons", headers=headers, params={"page_size": 25}
)
response.raise_for_status()
data = response.json()
print(f"Fetched {data['page']['size']} person(s)")
```

**Example response**

```json
{
  "items": [
    {
      "id": "990e8400-e29b-41d4-a716-446655440004",
      "firstName": "Jordan",
      "lastName": "Rivera",
      "primaryEmail": "jordan.rivera@example.com",
      "npn": "1234567",
      "npnVerified": true,
      "ssnLast4": "6789",
      "createdAt": "2026-03-18T18:19:20Z",
      "updatedAt": "2026-06-02T09:11:00Z"
    }
  ],
  "page": { "size": 1, "nextToken": null }
}
```

> **Got a `404`?** Check the `/identity` prefix — it's easy to send Identity calls to the bare host. **Got a `403`?** Your token is missing `identity.profiles.read`.

Two fields worth noting before you model anything:

-   A person's email is `primaryEmail`, not `email`. Firms use `email`. See [Email Fields](https://developer.agentsync.io/identity-api-overview#email-fields).
-   `npnVerified` tells you whether the NPN has been checked against NIPR — read it before treating an NPN as a join key.

## [Step 2: Read Contact Details](#step-2-read-contact-details)

Addresses and phones hang off a person or a firm:

```bash
curl -X GET \
  "https://api.sandbox.agentsync.io/identity/v2/persons/$PERSON_ID/addresses" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

| Resource | Types                         | Key fields                                                               |
| :------- | :---------------------------- | :----------------------------------------------------------------------- |
| Address  | MAILING, BUSINESS, PHYSICAL   | addressLine1, addressLine2, city, state, zip, county, country, preferred |
| Phone    | CELL, BUSINESS, RESIDENT, FAX | number (10 digits), extension, preferred                                 |

Both use the same `items` + `page` envelope, so your paging code carries over unchanged. Swap `persons` for `firms` to read a firm's contact details.

## [Step 3: Read Financial and Compliance Data](#step-3-read-financial-and-compliance-data)

**Bank accounts** can be read per producer, or account-wide:

```bash
# Every bank account visible to you, with owner context on each row
curl -X GET \
  "https://api.sandbox.agentsync.io/identity/v2/bank-accounts" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Each row carries `personId` or `firmId` plus `npn` where known, so you can reconcile your whole population without iterating producer IDs.

**E&O policies** work the same way via `/v2/persons/{personId}/insurance-policies`, carrying `carrier`, `policyNumber`, `totalLimit`, `effectiveDate`, and `expiry`.

### [Masked Fields](#masked-fields)

Sensitive values are masked by default and available in full only from dedicated endpoints, **each of which writes to a durable audit log on every access**:

| Field               | Default     | Full value                            |
| :------------------ | :---------- | :------------------------------------ |
| Person SSN          | ssnLast4    | GET /v2/persons/{personId}/ssn        |
| Firm FEIN           | ****{last4} | GET /v2/firms/{firmId}/fein           |
| Bank account number | ****{last4} | GET /v2/bank-accounts/{bankAccountId} |

Call these only when you genuinely need the value — for example, an account number at the point of payment processing. Don't fetch them to populate a cache.

## [Step 4: Keep Your Roster in Sync](#step-4-keep-your-roster-in-sync)

`GET /v2/persons` supports two filters built for sync pipelines:

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

-   `updated_since` — RFC3339 UTC timestamp, inclusive. Store your last run time as a high-water mark and overlap the window slightly.
-   `firm_id` — restrict to persons associated with a given firm.

`GET /v2/firms` accepts `updated_since` on the same terms, so a firm roster syncs the same way.

For near-real-time updates, subscribe to [Identity webhook events](https://developer.agentsync.io/identity-api-webhooks). The address and phone events carry the producer's **full current set**, so you can replace stored state without a follow-up read.

> **Identity events carry PII and financial data** — SSN, FEIN, bank account and routing numbers. Log the envelope (`id`, `type`, `timestamp`) rather than the `data` object, and restrict these subscriptions to systems approved for that class of data.

## [Step 5: Create a Producer (Optional)](#step-5-create-a-producer-optional)

Skip this step if your integration is read-only.

With `identity.profiles.write`, `POST /v2/persons` creates a producer identity. Set `sendInvite: true` to trigger the email invitation in the same call:

```bash
curl -X POST \
  "https://api.sandbox.agentsync.io/identity/v2/persons" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Priya",
    "lastName": "Raman",
    "primaryEmail": "priya.raman@example.com",
    "npn": "7654321",
    "sendInvite": true
  }'
```

The endpoint accepts an idempotency key header, so a retried request won't create a duplicate.

## [Pagination](#pagination)

Identity list responses are token-paginated. Pass the response's `page.nextToken` back as the `page_token` query parameter:

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

`page_size` defaults to 25 and must be `1–250` — the same limits Contracting enforces, so a paging loop written against one API works unchanged against the other. Out-of-range values are rejected with a `400` rather than clamped.

See [Pagination](https://developer.agentsync.io/api-pagination) for the full pattern.

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

Identity returns a machine-readable `code` on every error — branch on that, not the message:

```json
{
  "message": "Validation failed",
  "details": [
    { "param": "primaryEmail", "message": "must not be blank" }
  ],
  "code": "validation_failed"
}
```

| Code               | Status | Meaning                                             |
| :----------------- | :----- | :-------------------------------------------------- |
| validation_failed  | 400    | Field-level problems, itemised in details           |
| unauthorized       | 401    | Token missing, expired, or invalid                  |
| unauthorized_scope | 403    | Token lacks identity.profiles.read or .write        |
| resource_not_found | 404    | No such resource — or it belongs to another account |
| conflict           | 409    | Conflicts with an existing record                   |
| unprocessable      | 422    | Well-formed but semantically rejected               |
| rate_limited       | 429    | Back off — see Rate Limits                          |
| internal           | 500    | Server-side; safe to retry with backoff             |

Cross-account requests return `404`, never `403`, so resource existence isn't leaked across account boundaries. A `403` always means a missing scope.

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

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

```python
import requests


class IdentityClient:
    """Minimal read client for Identity v2."""

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

    def paginate(self, path, params=None):
        """Yield every item from a v2 token-paginated endpoint."""
        params = dict(params or {})
        params.setdefault("page_size", 250)  # 250 is the maximum the API accepts
        while True:
            response = self.session.get(f"{self.base}{path}", 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 persons(self, updated_since=None, firm_id=None):
        params = {}
        if updated_since:
            params["updated_since"] = updated_since
        if firm_id:
            params["firm_id"] = firm_id
        return self.paginate("/v2/persons", params)

    def addresses(self, person_id):
        return self.paginate(f"/v2/persons/{person_id}/addresses")

    def bank_accounts(self):
        """Account-wide, with personId/firmId owner context on each row."""
        return self.paginate("/v2/bank-accounts")


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

    for person in client.persons():
        addresses = client.addresses(person["id"])
        preferred = next((a for a in addresses if a.get("preferred")), None)
        print(person["npn"], person["primaryEmail"], preferred["city"] if preferred else "—")
```

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

-   [Identity API Overview](https://developer.agentsync.io/identity-api-overview) — full resource model and entity relationships
-   [Joining with the Contracting API](https://developer.agentsync.io/identity-api-overview#joining-with-the-contracting-api) — resolve Contracting `personId`/`firmId` against Identity
-   [Identity API Webhook Events](https://developer.agentsync.io/identity-api-webhooks) — the full event catalog with payload schemas
-   [Webhooks Quick Start](https://developer.agentsync.io/webhooks-quick-start-guide) — register an endpoint and validate delivery
-   [Pagination](https://developer.agentsync.io/api-pagination) · [Rate Limits](https://developer.agentsync.io/api-rate-limits) · [Traceability](https://developer.agentsync.io/api-traceability)