> ## Documentation Index
> Fetch the complete documentation index at: https://docs.range.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Onboard a counterparty

> Create a synthetic counterparty, attach an address, and verify the saved record with optional bank data and a document.

Create a counterparty and verify its saved address, with optional synthetic bank and document records.

## Prerequisites

* A workspace API key set up with the [shared quickstart](https://docs.range.org/introduction/quickstart.md), passed as a Bearer token in `RANGE_API_KEY`.
* Python with `httpx` installed: `python -m pip install httpx`.
* Permission to create synthetic records in the workspace selected by your key. These calls use the `/v2` Platform endpoints.

Use the [Platform test inputs](https://docs.range.org/resources/test-inputs.md#platform-api). Generate a unique `docs-test-` name and synthetic address on each run; use only fake CAD bank names without bank identifiers and the tiny generated PDF below. Uploads accept PDF, CSV or plain text up to 10 MB; the upload `type` accepts `kyc`, `kyb`, `contract`, `other`, `narrative`, or `statement`. The recorded IDs are examples from a completed run; always use the IDs returned by your own requests.

Run this setup once before the numbered steps:

```python theme={null}
import os
import uuid
import httpx

run_id = uuid.uuid4().hex
name = f"docs-test-{run_id}-counterparty"
address = "0x" + uuid.uuid5(uuid.NAMESPACE_URL, run_id).hex + "00000000"
api = httpx.Client(
    base_url="https://api.range.org",
    headers={"Authorization": f"Bearer {os.environ['RANGE_API_KEY']}",
             "User-Agent": "curl/8.4.0"},
    timeout=30,
)

def call(method, path, **kwargs):
    response = api.request(method, path, **kwargs)
    response.raise_for_status()
    return response.json() if response.content else None
```

## 1. Look for existing names

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/get-counterparties.md).

```python theme={null}
page = call("GET", "/v2/counterparties", params={"search": name, "limit": 50})
print(page)
while page["meta"]["next_cursor"]:
    page = call("GET", "/v2/counterparties", params={
        "search": name, "limit": 50, "cursor": page["meta"]["next_cursor"],
    })
    print(page)
```

Recorded response (HTTP 200), abbreviated where fields are omitted:

```json theme={null}
{
  "items": [],
  "meta": {
    "next_cursor": null
  }
}
```

`search` matches a case-insensitive substring of the name. Review candidates before creating a record. This lookup does not reserve a name or prevent duplicates; creation separately enforces workspace name uniqueness. A `null` `meta.next_cursor` ends pagination. The synthetic name returned an empty first page in the recorded run.

## 2. Create the counterparty

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/post-counterparties.md).

```python theme={null}
counterparty = call("POST", "/v2/counterparties", json={"name": name, "type": "other"})
counterparty_id = counterparty["id"]
```

Recorded response, abbreviated where fields are omitted:

```json theme={null}
{
  "id": "ffb2686d-0b1e-4dc9-ba0c-8a643fa411c9",
  "name": "docs-test-b5684b40251e420fb73b8583d43b7d24-counterparty",
  "type": "other",
  "addresses": [],
  "bank_accounts": [],
  "documents": []
}
```

Keep `id` as `counterparty_id` for every subsequent request.

Creating the same name again returns HTTP 409. The following POST body and rejection are recorded examples only, from a separate synthetic duplicate-name check. No duplicate request is needed for this procedure or included in the complete script.

```json theme={null}
{
  "name": "docs-test-b13b4f867ed04b8eb060101807a0252a-duplicate",
  "type": "other"
}
```

Recorded rejection:

```json theme={null}
{
  "message": "A counterparty named \"docs-test-b13b4f867ed04b8eb060101807a0252a-duplicate\" already exists in this workspace",
  "error": "Conflict",
  "statusCode": 409
}
```

## 3. Add an address

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/put-counterparties-by-id-addresses.md).

```python theme={null}
addresses = call("PUT", f"/v2/counterparties/{counterparty_id}/addresses", json={
    "operations": [{"action": "add", "address": address, "network": "ethereum",
                    "label": f"docs-test-{run_id}-address"}],
})
address_id = addresses[0]["id"]
```

Recorded response (HTTP 200), abbreviated where fields are omitted:

```json theme={null}
[
  {
    "id": "09a559a3-89a3-4c53-bcdc-dfd5cc9937f3",
    "address": "0x65f826f82b895a2bae35b2a7bb65b2dd00000000",
    "network": "ethereum",
    "label": "docs-test-b5684b40251e420fb73b8583d43b7d24-address"
  }
]
```

## 4. Read back the address

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/get-counterparties-by-id.md).

```python theme={null}
detail = call("GET", f"/v2/counterparties/{counterparty_id}")
assert detail["id"] == counterparty_id
assert any(row["id"] == address_id and row["address"].lower() == address
           for row in detail["addresses"])
```

Recorded response (HTTP 200), abbreviated where fields are omitted:

```json theme={null}
{
  "id": "ffb2686d-0b1e-4dc9-ba0c-8a643fa411c9",
  "name": "docs-test-b5684b40251e420fb73b8583d43b7d24-counterparty",
  "addresses": [
    {
      "id": "09a559a3-89a3-4c53-bcdc-dfd5cc9937f3",
      "address": "0x65f826f82b895a2bae35b2a7bb65b2dd00000000",
      "network": "ethereum",
      "label": "docs-test-b5684b40251e420fb73b8583d43b7d24-address"
    }
  ]
}
```

The onboarding check is complete when the detail response contains the returned address ID and your submitted address.

## 5. Optionally add a synthetic bank record

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/put-counterparties-by-id-bank-accounts.md).

```python theme={null}
bank_accounts = call("PUT", f"/v2/counterparties/{counterparty_id}/bank-accounts", json={
    "operations": [{"action": "add", "currency": "CAD",
                    "bank_name": f"docs-test-{run_id}-FAKE-BANK",
                    "account_name": f"docs-test-{run_id}-FAKE-HOLDER"}],
})
bank_id = bank_accounts[0]["id"]
```

Recorded response (HTTP 200), abbreviated where fields are omitted:

```json theme={null}
[
  {
    "id": "756dba46-5d24-4971-937e-396583d97daa",
    "currency": "CAD",
    "bank_name": "docs-test-b5684b40251e420fb73b8583d43b7d24-FAKE-BANK",
    "account_name": "docs-test-b5684b40251e420fb73b8583d43b7d24-FAKE-HOLDER"
  }
]
```

## 6. Read back the bank record

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/get-counterparties-by-id.md).

```python theme={null}
detail = call("GET", f"/v2/counterparties/{counterparty_id}")
assert any(row["id"] == bank_id and row["currency"] == "CAD"
           for row in detail["bank_accounts"])
```

Recorded response (HTTP 200), abbreviated where fields are omitted:

```json theme={null}
{
  "id": "ffb2686d-0b1e-4dc9-ba0c-8a643fa411c9",
  "bank_accounts": [
    {
      "id": "756dba46-5d24-4971-937e-396583d97daa",
      "currency": "CAD",
      "bank_name": "docs-test-b5684b40251e420fb73b8583d43b7d24-FAKE-BANK",
      "account_name": "docs-test-b5684b40251e420fb73b8583d43b7d24-FAKE-HOLDER"
    }
  ]
}
```

Skip steps 5 and 6 together if you do not need bank data.

## Document fixture

Define the tiny in-memory PDF before step 7:

```python theme={null}
def tiny_pdf():
    objects = [b"<< /Type /Catalog /Pages 2 0 R >>",
               b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
               b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 72 72] /Contents 4 0 R >>",
               b"<< /Length 0 >>\nstream\n\nendstream"]
    result, offsets = b"%PDF-1.4\n", [0]
    for number, obj in enumerate(objects, 1):
        offsets.append(len(result))
        result += f"{number} 0 obj\n".encode() + obj + b"\nendobj\n"
    xref = len(result)
    result += b"xref\n0 5\n0000000000 65535 f \n"
    result += b"".join(f"{offset:010d} 00000 n \n".encode() for offset in offsets[1:])
    return result + f"trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n".encode()
```

## 7. Optionally upload the document

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/post-counterparties-by-id-documents.md).

```python theme={null}
document_name = f"docs-test-{run_id}-document.pdf"
document = call("POST", f"/v2/counterparties/{counterparty_id}/documents",
                data={"type": "other", "name": document_name},
                files={"file": (document_name, tiny_pdf(), "application/pdf")})
document_id = document["id"]
```

Recorded response (HTTP 201), abbreviated where fields are omitted:

```json theme={null}
{
  "id": "bb15bd87-926e-4f2f-9d51-4a9e6bd10ed7",
  "name": "docs-test-b5684b40251e420fb73b8583d43b7d24-document.pdf",
  "type": "other",
  "mime_type": "application/pdf",
  "size_bytes": 412,
  "uploaded_at": "2026-10-20T22:31:48.215Z"
}
```

## 8. Read back the document metadata

[Endpoint reference](https://docs.range.org/platform-api/reference/v2/get-counterparties-by-id.md).

```python theme={null}
detail = call("GET", f"/v2/counterparties/{counterparty_id}")
assert any(row["id"] == document_id and row["mime_type"] == "application/pdf"
           for row in detail["documents"])
```

Recorded response (HTTP 200), abbreviated where fields are omitted:

```json theme={null}
{
  "id": "ffb2686d-0b1e-4dc9-ba0c-8a643fa411c9",
  "documents": [
    {
      "id": "bb15bd87-926e-4f2f-9d51-4a9e6bd10ed7",
      "name": "docs-test-b5684b40251e420fb73b8583d43b7d24-document.pdf",
      "type": "other",
      "mime_type": "application/pdf",
      "size_bytes": 412,
      "uploaded_at": "2026-10-20T22:31:48.215Z"
    }
  ]
}
```

Skip steps 7 and 8 together if you do not need a document.

## Complete runnable script

Save as `onboard_counterparty.py`. Set `RANGE_API_KEY` as in the shared quickstart, then run `python onboard_counterparty.py`; add `--with-bank --with-document` to exercise both optional branches. The script verifies the synthetic data, then removes its own children and counterparty. The step-by-step requests above keep the record until you delete it.

```python theme={null}
import os
import uuid
import httpx

run_id = uuid.uuid4().hex
name = f"docs-test-{run_id}-counterparty"
address = "0x" + uuid.uuid5(uuid.NAMESPACE_URL, run_id).hex + "00000000"
api = httpx.Client(
    base_url="https://api.range.org",
    headers={"Authorization": f"Bearer {os.environ['RANGE_API_KEY']}",
             "User-Agent": "curl/8.4.0"},
    timeout=30,
)

def call(method, path, **kwargs):
    response = api.request(method, path, **kwargs)
    response.raise_for_status()
    return response.json() if response.content else None

def tiny_pdf():
    objects = [b"<< /Type /Catalog /Pages 2 0 R >>",
               b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
               b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 72 72] /Contents 4 0 R >>",
               b"<< /Length 0 >>\nstream\n\nendstream"]
    result, offsets = b"%PDF-1.4\n", [0]
    for number, obj in enumerate(objects, 1):
        offsets.append(len(result))
        result += f"{number} 0 obj\n".encode() + obj + b"\nendobj\n"
    xref = len(result)
    result += b"xref\n0 5\n0000000000 65535 f \n"
    result += b"".join(f"{offset:010d} 00000 n \n".encode() for offset in offsets[1:])
    return result + f"trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n".encode()

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--with-bank", action="store_true")
parser.add_argument("--with-document", action="store_true")
args = parser.parse_args()
counterparty_id = address_id = bank_id = document_id = None
try:
    page = call("GET", "/v2/counterparties", params={"search": name, "limit": 50})
    print(page)
    while page["meta"]["next_cursor"]:
        page = call("GET", "/v2/counterparties", params={
            "search": name, "limit": 50, "cursor": page["meta"]["next_cursor"],
        })
        print(page)
    counterparty = call("POST", "/v2/counterparties", json={"name": name, "type": "other"})
    counterparty_id = counterparty["id"]
    addresses = call("PUT", f"/v2/counterparties/{counterparty_id}/addresses", json={
        "operations": [{"action": "add", "address": address, "network": "ethereum",
                        "label": f"docs-test-{run_id}-address"}],
    })
    address_id = addresses[0]["id"]
    detail = call("GET", f"/v2/counterparties/{counterparty_id}")
    assert detail["id"] == counterparty_id
    assert any(row["id"] == address_id and row["address"].lower() == address
               for row in detail["addresses"])
    if args.with_bank:
        bank_accounts = call("PUT", f"/v2/counterparties/{counterparty_id}/bank-accounts", json={
            "operations": [{"action": "add", "currency": "CAD",
                            "bank_name": f"docs-test-{run_id}-FAKE-BANK",
                            "account_name": f"docs-test-{run_id}-FAKE-HOLDER"}],
        })
        bank_id = bank_accounts[0]["id"]
        detail = call("GET", f"/v2/counterparties/{counterparty_id}")
        assert any(row["id"] == bank_id and row["currency"] == "CAD"
                   for row in detail["bank_accounts"])
    if args.with_document:
        document_name = f"docs-test-{run_id}-document.pdf"
        document = call("POST", f"/v2/counterparties/{counterparty_id}/documents",
                        data={"type": "other", "name": document_name},
                        files={"file": (document_name, tiny_pdf(), "application/pdf")})
        document_id = document["id"]
        detail = call("GET", f"/v2/counterparties/{counterparty_id}")
        assert any(row["id"] == document_id and row["mime_type"] == "application/pdf"
                   for row in detail["documents"])
    print({"verified_counterparty_id": counterparty_id})
finally:
    try:
        if document_id:
            call("DELETE", f"/v2/counterparties/{counterparty_id}/documents/{document_id}")
        if bank_id:
            call("PUT", f"/v2/counterparties/{counterparty_id}/bank-accounts",
                 json={"operations": [{"action": "remove", "id": bank_id}]})
        if address_id:
            call("PUT", f"/v2/counterparties/{counterparty_id}/addresses",
                 json={"operations": [{"action": "remove", "id": address_id}]})
    finally:
        try:
            if counterparty_id:
                call("DELETE", f"/v2/counterparties/{counterparty_id}")
        finally:
            api.close()
```

Cleanup references: [remove a document](https://docs.range.org/platform-api/reference/v2/delete-counterparties-by-id-documents-by-doc-id.md) and [delete the counterparty](https://docs.range.org/platform-api/reference/v2/delete-counterparties-by-id.md). For failed requests, use [Errors and rate limits](https://docs.range.org/resources/errors-and-rate-limits.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.