Portfolio Monitoring: API Quickstart

This guide explains how to use the Baselayer Portfolio Monitoring API to:

  1. Search for and verify a business
  2. Add that business to a monitored portfolio
  3. Retrieve ongoing updates and changes for that business
  4. Drill into field-level changes over time

This guide is designed to be used alongside the provided Postman collection (access the file here).

For background on portfolios, groups, policies, and how to design monitoring, see Portfolio Monitoring: Basics.


Overview

The Portfolio Monitoring API lets you:

  • Search for a business by name and address
  • Create a portfolio item from that search result
  • Poll portfolio updates to see what has changed (registrations, officers, scores, watchlists, etc.)
  • Drill into changes for a specific portfolio item to see field-level diffs

High-Level Flow

  1. Search for a business

    POST /searches (synchronous search)

  2. Add the business to your portfolio

    POST /portfolio/items

  3. Fetch portfolio updates

    GET /portfolio/updates

  4. Paginate through updates

    Use the X-Next-Cursor response header

  5. Inspect changes for a specific item (optional)

    GET /portfolio/items/{item_id}/changes


Base URL & Authentication

Base URL

Use the appropriate environment base URL in your integration:

  • Production: https://api.baselayer.com
  • Staging (examples in this doc): https://api.staging.baselayer.com

In the examples below, we refer to this as:

{{baseUrl}}

(e.g., {{baseUrl}} = https://api.staging.baselayer.com)

Authentication

All requests are authenticated using an API key via the X-API-Key header:

X-API-Key: {{apiKey}}

In Postman, this is configured at the collection level using the API Key auth type with:

  • Key: X-API-Key
  • Value: {{apiKey}}

You can then define {{apiKey}} as an environment variable.


Step 1 – Business Search

Use the Business Search endpoint to look up and verify a business by name and address.

Endpoint

POST /searches
Content-Type: application/json

Request Body Example

{
  "name": "22645 anza avenue statutory trust",
  "address": "1718 capitol avenue cheyenne wy 82001"
}

Sample Response (Truncated)

{
  "id": "4dc4fd78-b7de-4ee8-ade0-dbd455e48ca6",
  "state": "COMPLETED",
  "name": "22645 anza avenue statutory trust",
  "address": "1718 capitol avenue cheyenne wy 82001",
  "search_address": {
    "id": "587c35d9-7ec4-48bb-a421-7b20c4db005c",
    "street": "1716 Capitol Ave Ste 100",
    "city": "Cheyenne",
    "state": "WY",
    "zip": "82001",
    "latitude": 41.13403,
    "longitude": -104.81626,
    "rdi": "Commercial",
    "deliverable": true,
    "cmra": false,
    "url": "https://api.staging.baselayer.com/addresses/587c35d9-7ec4-48bb-a421-7b20c4db005c",
    "delivery_type": "STREET"
  },
  "business_name_match": "EXACT",
  "business_address_match": "EXACT",
  "scores": [
    {
      "type": "kyb",
      "score": 100.0,
      "rating": "A"
    },
    {
      "type": "risk",
      "score": 100.0,
      "rating": "A"
    }
  ],
  "business": {
    "id": "428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
    "name": "22645 Anza Avenue Statutory Trust",
    "structure": "TRUST",
    "addresses": [
      {
        "id": "96ab5659-0a69-415e-a5f7-76e992ae7bd0",
        "street": "1718 Capitol Ave",
        "city": "Cheyenne",
        "state": "WY",
        "zip": "82001"
      },
      {
        "id": "587c35d9-7ec4-48bb-a421-7b20c4db005c",
        "street": "1716 Capitol Ave Ste 100",
        "city": "Cheyenne",
        "state": "WY",
        "zip": "82001"
      }
    ],
    "incorporation_state": "WY",
    "incorporation_date": "2021-01-01",
    "months_in_business": 58,
    "registrations": [/* ... */],
    "business_officers": [/* ... */],
    "watchlist_hits": [
      {
        "code": "IEO",
        "name": "IRS Exempt Organizations List",
        "count": 0,
        "details": []
      }
    ],
    "url": "https://api.staging.baselayer.com/businesses/428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
    "console_url": "https://console.staging.baselayer.com/business/428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
    "address": "1716 Capitol Ave Ste 100, Cheyenne, WY 82001"
  },
  "url": "https://api.staging.baselayer.com/searches/4dc4fd78-b7de-4ee8-ade0-dbd455e48ca6",
  "status_url": "https://api.staging.baselayer.com/searches/4dc4fd78-b7de-4ee8-ade0-dbd455e48ca6/status",
  "business_url": "https://api.staging.baselayer.com/businesses/428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
  "console_url": "https://console.staging.baselayer.com/business/428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3?searchId=4dc4fd78-b7de-4ee8-ade0-dbd455e48ca6"
}

Key Fields to Capture

From this response you’ll typically store:

  • id – the search id (e.g. 4dc4fd78-b7de-4ee8-ade0-dbd455e48ca6)
  • business.id – the business id (e.g. 428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3)
  • scores – KYB and risk scores at time of search
  • business details – structure, registrations, officers, addresses, watchlists, etc.

The business search must be completed and must have matched to a business profile to be able to add the search as a portfolio Item.

You’ll use the search id in the next step to create a portfolio item.


Step 2 – Add the Business to Your Portfolio

Once you’ve verified the search result, add it to the portfolio for ongoing monitoring.

Endpoint

POST /portfolio/items
Content-Type: application/json

Request Body Example

{
  "business_search_id": "4dc4fd78-b7de-4ee8-ade0-dbd455e48ca6"
}

Note: business_search_id should be the id field from the search response.

Sample Response

{
  "id": "825667a6-2fe0-432d-ae59-edddab3ce068",
  "business_id": "428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
  "business": {
    "id": "428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
    "name": "22645 Anza Avenue Statutory Trust",
    "address": "1716 Capitol Ave Cheyenne WY 82001",
    "incorporation_date": "2021-01-01",
    "structure": "TRUST",
    "ein": null,
    "incorporation_state": "WY",
    "phone_number": null,
    "email": null,
    "website": null,
    "url": "https://api.staging.baselayer.com/businesses/428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
    "console_url": "https://console.staging.baselayer.com/business/428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3"
  },
  "group_id": null,
  "name": "22645 anza avenue statutory trust",
  "address": "1718 capitol avenue cheyenne wy 82001",
  "business_name_match": "EXACT",
  "business_address_match": "EXACT",
  "verified": true,
  "scores": [
    {
      "type": "kyb",
      "score": 100,
      "rating": "A"
    },
    {
      "type": "risk",
      "score": 100,
      "rating": "A"
    }
  ],
  "created_at": "2025-11-26T20:27:41.824138Z",
  "last_monitored_at": null
}

Key Fields to Capture

  • idportfolio item id (e.g. 825667a6-2fe0-432d-ae59-edddab3ce068)
  • business_id – underlying Baselayer business id
  • scores – KYB & risk scores at the time the business was added to the portfolio

You’ll use the portfolio id later to request detailed changes for this item.


Step 3 – Fetch Portfolio Updates

Portfolio updates capture changes over time for items in your portfolio. These include:

  • Registration standing changes (e.g., Good → Suspended → Active)
  • KYB and risk score changes
  • Address and officer changes
  • Watchlist changes (OFAC, PEP, etc.)

Endpoint

GET /portfolio/updates

Optional query parameters:

  • limit – maximum number of updates to return per page
  • cursor – pagination cursor (see the pagination section below)

Sample Response

[
  {
    "item": {
      "id": "825667a6-2fe0-432d-ae59-edddab3ce068",
      "name": "22645 anza avenue statutory trust",
      "business_id": "428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
      "group_id": null,
      "assignee": null,
      "scores": [
        {
          "type": "kyb",
          "score": 100.0,
          "rating": "A"
        },
        {
          "type": "risk",
          "score": 100.0,
          "rating": "A"
        }
      ],
      "created_at": "2025-11-26T20:27:41.824138Z",
      "item_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068",
      "group_url": null,
      "console_url": "https://console.staging.baselayer.com/portfolio-monitoring/items/825667a6-2fe0-432d-ae59-edddab3ce068"
    },
    "from_snapshot_id": "b7d967d7-dbff-405d-8dc2-6a14d1d97c72",
    "to_snapshot_id": "4c2cd1ff-f92e-496e-a926-9f58b5f42478",
    "change_count": 7,
    "notification_count": 7,
    "notification_summaries": [
      { "attribute": "new_registrations", "count": 0 },
      { "attribute": "registration_standing", "count": 1 },
      { "attribute": "addresses", "count": 1 },
      { "attribute": "officers", "count": 1 },
      { "attribute": "alternative_names", "count": 0 },
      { "attribute": "kyb_score", "count": 1 },
      { "attribute": "risk_score", "count": 1 },
      { "attribute": "identity_network", "count": 0 },
      { "attribute": "pep", "count": 1 },
      { "attribute": "ofac", "count": 1 },
      { "attribute": "liens", "count": 0 },
      { "attribute": "litigations", "count": 0 },
      { "attribute": "bankruptcies", "count": 0 },
      { "attribute": "website_analysis", "count": 0 }
    ],
    "created_at": "2025-11-26T20:27:41.980342Z",
    "item_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068",
    "from_snapshot_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/snapshots/b7d967d7-dbff-405d-8dc2-6a14d1d97c72",
    "to_snapshot_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/snapshots/4c2cd1ff-f92e-496e-a926-9f58b5f42478",
    "diff_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/diff?from_snapshot_id=b7d967d7-dbff-405d-8dc2-6a14d1d97c72&to_snapshot_id=4c2cd1ff-f92e-496e-a926-9f58b5f42478",
    "notifications_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/diff?from_snapshot_id=b7d967d7-dbff-405d-8dc2-6a14d1d97c72&to_snapshot_id=4c2cd1ff-f92e-496e-a926-9f58b5f42478&format=notifications",
    "notifications": [
      {
        "message": {
          "text": "State Registration - Wyoming Standing changed from Suspended to Active. (Domestic)"
        },
        "notifiable_attribute": "registration_standing"
      },
      {
        "message": {
          "text": "KYB Score increased from C to B."
        },
        "notifiable_attribute": "kyb_score"
      },
      {
        "message": {
          "text": "Risk Score decreased from B to A."
        },
        "notifiable_attribute": "risk_score"
      },
      {
        "message": {
          "text": "Address removed: 123 Sandbox Lane, Test City, CA 90210."
        },
        "notifiable_attribute": "addresses"
      },
      {
        "message": {
          "text": "Officer removed: Jane Sandbox Smith (Chief Sandbox Officer)."
        },
        "notifiable_attribute": "officers"
      },
      {
        "message": {
          "text": "OFAC watchlist hit removed: 1 hit."
        },
        "notifiable_attribute": "ofac"
      },
      {
        "message": {
          "text": "PEP watchlist hit removed: 1 hit."
        },
        "notifiable_attribute": "pep"
      }
    ]
  },
  {
    "item": {
      "id": "825667a6-2fe0-432d-ae59-edddab3ce068",
      "name": "22645 anza avenue statutory trust",
      "business_id": "428ee4cc-39c0-4f8f-981a-95dbe5f8bbe3",
      "group_id": null,
      "assignee": null,
      "scores": [
        {
          "type": "kyb",
          "score": 100.0,
          "rating": "A"
        },
        {
          "type": "risk",
          "score": 100.0,
          "rating": "A"
        }
      ],
      "created_at": "2025-11-26T20:27:41.824138Z",
      "item_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068",
      "group_url": null,
      "console_url": "https://console.staging.baselayer.com/portfolio-monitoring/items/825667a6-2fe0-432d-ae59-edddab3ce068"
    },
    "from_snapshot_id": "cfbd361c-98b4-411d-850c-4a7bdcc311b9",
    "to_snapshot_id": "b7d967d7-dbff-405d-8dc2-6a14d1d97c72",
    "change_count": 7,
    "notification_count": 7,
    "notification_summaries": [
      { "attribute": "new_registrations", "count": 0 },
      { "attribute": "registration_standing", "count": 1 },
      { "attribute": "addresses", "count": 1 },
      { "attribute": "officers", "count": 1 },
      { "attribute": "alternative_names", "count": 0 },
      { "attribute": "kyb_score", "count": 1 },
      { "attribute": "risk_score", "count": 1 },
      { "attribute": "identity_network", "count": 0 },
      { "attribute": "pep", "count": 1 },
      { "attribute": "ofac", "count": 1 },
      { "attribute": "liens", "count": 0 },
      { "attribute": "litigations", "count": 0 },
      { "attribute": "bankruptcies", "count": 0 },
      { "attribute": "website_analysis", "count": 0 }
    ],
    "created_at": "2025-11-19T20:27:41.980342Z",
    "item_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068",
    "from_snapshot_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/snapshots/cfbd361c-98b4-411d-850c-4a7bdcc311b9",
    "to_snapshot_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/snapshots/b7d967d7-dbff-405d-8dc2-6a14d1d97c72",
    "diff_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/diff?from_snapshot_id=cfbd361c-98b4-411d-850c-4a7bdcc311b9&to_snapshot_id=b7d967d7-dbff-405d-8dc2-6a14d1d97c72",
    "notifications_url": "https://api.staging.baselayer.com/portfolio/items/825667a6-2fe0-432d-ae59-edddab3ce068/diff?from_snapshot_id=cfbd361c-98b4-411d-850c-4a7bdcc311b9&to_snapshot_id=b7d967d7-dbff-405d-8dc2-6a14d1d97c72&format=notifications",
    "notifications": [
      {
        "message": {
          "text": "State Registration - Wyoming Standing changed from Good Standing to Suspended. (Domestic)"
        },
        "notifiable_attribute": "registration_standing"
      },
      {
        "message": {
          "text": "KYB Score decreased from A to C."
        },
        "notifiable_attribute": "kyb_score"
      },
      {
        "message": {
          "text": "Risk Score increased from A to B."
        },
        "notifiable_attribute": "risk_score"
      },
      {
        "message": {
          "text": "Address added: 123 Sandbox Lane, Test City, CA 90210."
        },
        "notifiable_attribute": "addresses"
      },
      {
        "message": {
          "text": "Officer added: Jane Sandbox Smith (Chief Sandbox Officer)."
        },
        "notifiable_attribute": "officers"
      },
      {
        "message": {
          "text": "OFAC watchlist hit added: 1 hit."
        },
        "notifiable_attribute": "ofac"
      },
      {
        "message": {
          "text": "PEP watchlist hit added: 1 hit."
        },
        "notifiable_attribute": "pep"
      }
    ]
  }
]

Interpreting an Update

Each array element represents changes between two snapshots of a portfolio item:

  • item – basic portfolio item metadata
  • from_snapshot_id / to_snapshot_id – snapshot identifiers for diffing
  • notification_summaries – counts of changes per attribute category
  • notifications – human-readable change messages (good for dashboards, emails, etc.)
  • diff_url / notifications_url – API URLs to fetch structured diffs or notifications for this change window

You can use notification_summaries to quickly understand what kind of changes occurred, then drill into notifications or /portfolio/items/{item_id}/changes for details.


Step 4 – Pagination with X-Next-Cursor

The /portfolio/updates endpoint uses cursor-based pagination.

When there are more updates available, the response headers include something like:

X-Next-Cursor: eyJjcmVhdGVkX2F0IjoiMjAyNC0wMS0xNVQxMDozMDowMFoiLCJpZCI6IjEyMzQ1In0=

You can pass that value back to the endpoint as a cursor query parameter, for example:

GET /portfolio/updates?limit=50&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNC0wMS0xNVQxMDozMDowMFoiLCJpZCI6IjEyMzQ1In0=

Recommended Pattern

  1. Call GET /portfolio/updates?limit=50
  2. Process all updates in the response
  3. If the X-Next-Cursor header is present, call the endpoint again with cursor=<value>
  4. Repeat until no X-Next-Cursor header is returned

This pattern works well in a scheduled job (for example, every few minutes) to ingest new portfolio changes.


Step 5 – Inspect Field-Level Changes for a Portfolio Item

If you need structured, field-level information about what changed for a particular item, use the item changes endpoint.

Endpoint

GET /portfolio/items/{item_id}/changes

Where {item_id} is the portfolio item id (for example, 825667a6-2fe0-432d-ae59-edddab3ce068).

Sample Response

[
  {
    "path": "/business/corporate_registrations/0/standing",
    "operation": "replace",
    "old_value": "Suspended",
    "new_value": "Active",
    "changed_at": "2025-11-26T20:27:41.980342Z"
  },
  {
    "path": "/scores/0",
    "operation": "replace",
    "old_value": {
      "type": "kyb",
      "score": 65.0,
      "rating": "C"
    },
    "new_value": {
      "type": "kyb",
      "score": 75.0,
      "rating": "B"
    },
    "changed_at": "2025-11-26T20:27:41.980342Z"
  },
  {
    "path": "/scores/1",
    "operation": "replace",
    "old_value": {
      "type": "risk",
      "score": 45.0,
      "rating": "B"
    },
    "new_value": {
      "type": "risk",
      "score": 30.0,
      "rating": "A"
    },
    "changed_at": "2025-11-26T20:27:41.980342Z"
  },
  {
    "path": "/business/addresses/0",
    "operation": "remove",
    "old_value": {
      "id": "00000000-0000-0000-0000-000000000002",
      "street": "123 Sandbox Lane",
      "city": "Test City",
      "state": "CA",
      "zip": "90210",
      "latitude": 34.0901,
      "longitude": -118.4065,
      "rdi": null,
      "deliverable": null,
      "cmra": false,
      "url": null,
      "delivery_type": null,
      "sources": []
    },
    "new_value": null,
    "changed_at": "2025-11-26T20:27:41.980342Z"
  },
  {
    "path": "/business/watchlists/0",
    "operation": "remove",
    "old_value": {
      "code": "OFAC",
      "name": "Department of Treasury, Office of Foreign Assets Control",
      "count": 1,
      "details": [
        {
          "name": "Sandbox OFAC Match",
          "address": "123 Test Street, Test City, CA 90210",
          "match_score": 0.85
        }
      ]
    },
    "new_value": null,
    "changed_at": "2025-11-26T20:27:41.980342Z"
  },
  {
    "path": "/business/watchlists/1",
    "operation": "remove",
    "old_value": {
      "code": "PEP",
      "name": "Politically Exposed Persons",
      "count": 1,
      "details": [
        {
          "name": "Sandbox PEP Match",
          "country": "US",
          "position": "Test Official",
          "match_score": 0.78
        }
      ]
    },
    "new_value": null,
    "changed_at": "2025-11-26T20:27:41.980342Z"
  },
  {
    "path": "/business/business_officers/0",
    "operation": "remove",
    "old_value": {
      "name": "Jane Sandbox Smith",
      "titles": [
        "Chief Sandbox Officer"
      ],
      "states": [
        "CA"
      ]
    },
    "new_value": null,
    "changed_at": "2025-11-26T20:27:41.980342Z"
  },
  {
    "path": "/business/corporate_registrations/0/standing",
    "operation": "replace",
    "old_value": "Good Standing",
    "new_value": "Suspended",
    "changed_at": "2025-11-19T20:27:41.980342Z"
  },
  {
    "path": "/scores/0",
    "operation": "replace",
    "old_value": {
      "type": "kyb",
      "score": 85.0,
      "rating": "A"
    },
    "new_value": {
      "type": "kyb",
      "score": 65.0,
      "rating": "C"
    },
    "changed_at": "2025-11-19T20:27:41.980342Z"
  },
  {
    "path": "/scores/1",
    "operation": "replace",
    "old_value": {
      "type": "risk",
      "score": 20.0,
      "rating": "A"
    },
    "new_value": {
      "type": "risk",
      "score": 45.0,
      "rating": "B"
    },
    "changed_at": "2025-11-19T20:27:41.980342Z"
  },
  {
    "path": "/business/addresses/0",
    "operation": "add",
    "old_value": null,
    "new_value": {
      "id": "00000000-0000-0000-0000-000000000002",
      "street": "123 Sandbox Lane",
      "city": "Test City",
      "state": "CA",
      "zip": "90210",
      "latitude": 34.0901,
      "longitude": -118.4065,
      "rdi": null,
      "deliverable": null,
      "cmra": false,
      "url": null,
      "delivery_type": null,
      "sources": []
    },
    "changed_at": "2025-11-19T20:27:41.980342Z"
  },
  {
    "path": "/business/watchlists/0",
    "operation": "add",
    "old_value": null,
    "new_value": {
      "code": "OFAC",
      "name": "Department of Treasury, Office of Foreign Assets Control",
      "count": 1,
      "details": [
        {
          "name": "Sandbox OFAC Match",
          "address": "123 Test Street, Test City, CA 90210",
          "match_score": 0.85
        }
      ]
    },
    "changed_at": "2025-11-19T20:27:41.980342Z"
  },
  {
    "path": "/business/watchlists/1",
    "operation": "add",
    "old_value": null,
    "new_value": {
      "code": "PEP",
      "name": "Politically Exposed Persons",
      "count": 1,
      "details": [
        {
          "name": "Sandbox PEP Match",
          "country": "US",
          "position": "Test Official",
          "match_score": 0.78
        }
      ]
    },
    "changed_at": "2025-11-19T20:27:41.980342Z"
  },
  {
    "path": "/business/business_officers/0",
    "operation": "add",
    "old_value": null,
    "new_value": {
      "name": "Jane Sandbox Smith",
      "titles": [
        "Chief Sandbox Officer"
      ],
      "states": [
        "CA"
      ]
    },
    "changed_at": "2025-11-19T20:27:41.980342Z"
  }
]

Interpreting the Change Objects

Each object represents a single change:

  • path – JSON path within the portfolio item document that changed
  • operation"add", "remove", or "replace"
  • old_value – value before the change (or null for add)
  • new_value – value after the change (or null for remove)
  • changed_at – timestamp of the change

These are ideal for:

  • Internal audit logs
  • Driving your own notification or workflow system
  • Building detailed “change history” or “activity log” UIs

End-to-End User Flow Summary

A typical integration to monitor a business with Baselayer looks like this:

  1. Search and verify

    • Call POST /searches with the business name and address.
    • Review business, scores, and match quality fields.
  2. Create a portfolio item

    • Call POST /portfolio/items with business_search_id from step 1.
    • Store the returned portfolio id.
  3. Poll for updates

    • Periodically call GET /portfolio/updates?limit=50.
    • Use X-Next-Cursor to paginate through all updates.
    • Use notification_summaries and notifications to decide when to alert or trigger workflows.
  4. Drill into details as needed

    • Call GET /portfolio/items/{item_id}/changes for item-level diffs.
    • Optionally use diff_url and notifications_url from each update for more specialized views.

With this flow implemented, your system will have continuous visibility into changes to the businesses you monitor (scores, registrations, officers, addresses, and watchlists) via Baselayer’s Portfolio Monitoring API.