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

# Reading Agent Activity

> Read the visits Baselayer saw at your site, what each agent did, and how far its identity got, with your API key.

For engineers and analysts. Once [baselayer.js](https://docs.baselayer.com/docs/kya-acceptance-quickstart.md) and your KYA SDK are running, the acceptance API answers what agents did at your site: every visit, each visit's timeline and decisions, and how many visits reached each identity stage.

## What You Can Learn

| Question | Where the answer is |
| - | - |
| Which agents visited this week? | [Your visits](#list-your-visits), filtered by `presence_kind=agent` |
| What did one agent do? | [A visit's timeline](#read-a-visits-timeline) |
| How many agent visits presented a KYA credential? | [The identity funnel](#count-visits-by-identity), at the `credentialed` stage |
| Which visits ended in a purchase? | [Your visits](#list-your-visits), filtered by `outcome=converted` |

Your developer can pull these into your analytics tool.

## Authenticating

Send your API key in the `X-API-Key` header, as for every Baselayer request. See [Authentication](https://docs.baselayer.com/docs/authentication.md). The visits reads need the `acceptance_visits:read` permission.

Every read answers for the application your key belongs to: a sandbox key reads your sandbox application, and a production key reads your production application. You never pass an application ID.

## Lists and Pages

Every list returns a bare JSON array, a page at a time.

| Parameter | Meaning |
| - | - |
| `limit` | Records per page, 1 to 1000. Defaults to 10. |
| `cursor` | The `X-Next-Cursor` value from the previous page. Cannot be used with `offset`. |
| `offset` | Records to skip. Cannot be used with `cursor`. |
| `sort` | The field to sort on. Each list names its own fields and its default. |
| `sort_direction` | `asc` or `desc`. Defaults to the field's own direction. |

Each page carries these headers:

| Header | Meaning |
| - | - |
| `X-Next-Cursor` | Pass it as `cursor` to get the next page. Absent on the last page. |
| `X-Total-Count` | Records matching the request. Sent on the first cursor page and on every offset page. |
| `X-Total-Pages` | Pages at the current `limit`. Sent with `X-Total-Count`. |

Use `cursor` to walk a long list: a page never skips or repeats a record when new visits arrive.

## Dates and Time Zones

The visits list and the identity funnel take a date range:

| Parameter | Meaning |
| - | - |
| `start_date` | First day, `YYYY-MM-DD`, inclusive |
| `end_date` | Last day, `YYYY-MM-DD`, inclusive |
| `tz` | The IANA time zone the days are read in, such as `America/New_York`. Defaults to `UTC`. |

The identity funnel reads the last 30 days when you send no dates, and refuses a range over 366 days with `422` `date_range_too_large`.

## Freshness and Retries

Visits are rebuilt about once a minute, so a visit, its timeline and its decisions show a record about a minute after it is reported.

When the acceptance data cannot be read for a moment, a read answers `503` with a `Retry-After` header and this body. Wait that many seconds and send the request again.

```json theme={null}
{
  "type": "acceptance_reads_unavailable",
  "code": 7252,
  "message": "Acceptance data is temporarily unavailable. Retry after the Retry-After interval.",
  "metadata": {},
  "request_id": "3c9e5b1a7d244f8ea6b092d1e4c7f305"
}
```

Branch your code on `type`. `message` is for people to read.

## List Your Visits

A visit is one visitor's run of records at your site, from the browser and your server alike, until 30 minutes pass without one. Visits are newest first by default (`sort=started_at`, or `sort=last_seen_at`).

Filters take one value or several (`identity_level=L2&identity_level=L3`): `presence_kind`, `presence_trust`, `identity_level`, `transport`, `state`, `outcome`, `agent_key_thumbprint`, `credential_id` and `credential_subject`. Values of one filter match any of them, and different filters must all match.

```bash theme={null}
curl "https://api.baselayer.com/acceptance/visits?presence_kind=agent&start_date=2026-05-01&end_date=2026-05-07&tz=America/New_York&limit=1" \
  -H "X-API-Key: $BASELAYER_API_KEY"
```

```json theme={null}
[
  {
    "visit_id": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "environment": "production",
    "transport": "api",
    "state": "ended",
    "outcome": "converted",
    "first_step": "browse",
    "last_step": "checkout",
    "presence": {
      "kind": "agent",
      "trust": "credentialed",
      "agent_platform": "Example Agent"
    },
    "identity_level": "L3",
    "agent": {
      "agent_key_thumbprint": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
      "agent_key_kid": null,
      "agent_key_source": "kya",
      "agent_platform": "Example Agent",
      "first_seen_at": "2026-05-04T14:58:02Z",
      "visit_count": 4
    },
    "credential": {
      "credential_id": "urn:uuid:2b1e4c6a-8f3d-4e2b-9a71-5c0d3e8f1a22",
      "subject": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
    },
    "decision_count": 3,
    "started_at": "2026-05-04T15:02:11Z",
    "last_seen_at": "2026-05-04T15:06:40Z",
    "ended_at": "2026-05-04T15:36:40Z",
    "funnel_stage": "converted",
    "identity_stage": "credentialed",
    "recognized_by": "credential",
    "risk_score": null,
    "journey": null,
    "detection_label": null
  }
]
```

`presence` is what is behind the visit now: `kind` (`human`, `agent`, `crawler`, `automation` or `unknown`) and `trust`, how strongly the evidence backs it, strongest first: `credentialed`, `verified`, `declared`, `scored`, `unobserved` and `none`. `journey` lists the pages a browser visit viewed, and is null for a visit with no page views.

`credential.subject` is the credential's `sub`: the same site-specific customer ID your credential check sees, so you can match a visit to your customer. `identity.subject` is the same value. See [Matching Customers](https://docs.baselayer.com/docs/kya-matching-customers.md).

## Get One Visit

`GET /acceptance/visits/{visit_id}` returns the visit with two more members: `identity`, who the visitor is as far as the evidence goes, and `detection`, what browser detection made of it (null before any detection result).

```bash theme={null}
curl "https://api.baselayer.com/acceptance/visits/01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b" \
  -H "X-API-Key: $BASELAYER_API_KEY"
```

```json theme={null}
{
  "visit_id": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
  "environment": "production",
  "transport": "api",
  "state": "ended",
  "outcome": "converted",
  "first_step": "browse",
  "last_step": "checkout",
  "presence": {
    "kind": "agent",
    "trust": "credentialed",
    "agent_platform": "Example Agent"
  },
  "identity_level": "L3",
  "agent": {
    "agent_key_thumbprint": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
    "agent_key_kid": null,
    "agent_key_source": "kya",
    "agent_platform": "Example Agent",
    "first_seen_at": "2026-05-04T14:58:02Z",
    "visit_count": 4
  },
  "credential": {
    "credential_id": "urn:uuid:2b1e4c6a-8f3d-4e2b-9a71-5c0d3e8f1a22",
    "subject": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
  },
  "decision_count": 3,
  "started_at": "2026-05-04T15:02:11Z",
  "last_seen_at": "2026-05-04T15:06:40Z",
  "ended_at": "2026-05-04T15:36:40Z",
  "funnel_stage": "converted",
  "identity_stage": "credentialed",
  "recognized_by": "credential",
  "risk_score": null,
  "journey": null,
  "detection_label": null,
  "identity": {
    "stage": "credentialed",
    "agent_name": "Example Agent",
    "operator": null,
    "recognized_by": "credential",
    "signature_status": null,
    "identity_level": "L3",
    "verification_level": 3,
    "subject": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
    "credential_id": "urn:uuid:2b1e4c6a-8f3d-4e2b-9a71-5c0d3e8f1a22",
    "agent_key_thumbprint": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
    "seen_before": null
  },
  "detection": null
}
```

## Read a Visit's Timeline

`GET /acceptance/visits/{visit_id}/timeline` lists what happened, oldest first (`sort=timeline`, `sort_direction=asc`; ties break on `record_id`). Each item's `kind` says what it is:

| `kind` | What it is |
| - | - |
| `browser_event` | A record from baselayer.js, such as a `page_view` or an `interaction` |
| `detection_update` | A detection result that arrived after the page |
| `request` | A request your server saw, with its `decision` when your policy ran on it |
| `event` | A funnel event your server reported, such as a `purchase` |

```bash theme={null}
curl "https://api.baselayer.com/acceptance/visits/01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/timeline?limit=50" \
  -H "X-API-Key: $BASELAYER_API_KEY"
```

```json theme={null}
[
  {
    "record_id": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7c",
    "occurred_at": "2026-05-04T15:02:11Z",
    "path_template": "/products/{id}",
    "kind": "request",
    "method": "GET",
    "status": 200,
    "latency_ms": 41,
    "transport": "api",
    "decision": {
      "acceptance_decision_id": "6f1d2a8e-3b4c-4d5e-8f60-718293a4b5c6",
      "outcome": "allow",
      "merchant_outcome": null,
      "enforcement_mode": "enforce",
      "step": "browse",
      "rule": "everyone",
      "reasons": [
        "credential_verified"
      ]
    }
  },
  {
    "record_id": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7d",
    "occurred_at": "2026-05-04T15:06:40Z",
    "path_template": "/checkout/place",
    "kind": "event",
    "event_kind": "purchase",
    "conversion": "order",
    "order_reference": "order-1001"
  }
]
```

## Read a Visit's Decisions

`GET /acceptance/visits/{visit_id}/decisions` lists every decision your KYA SDK made during the visit, newest first (`sort=decided_at`, `sort_direction=desc`; ties break on the decision's ID), in full: the presence it ran on, the evidence, the policy rule that fired, the credential and Baselayer's own check of the decision in `integrity`. Filter with `step` and `outcome`. This read needs the `acceptance_decisions:read` permission.

```bash theme={null}
curl "https://api.baselayer.com/acceptance/visits/01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/decisions?limit=1" \
  -H "X-API-Key: $BASELAYER_API_KEY"
```

```json theme={null}
[
  {
    "report_id": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a80",
    "decided_at": "2026-05-04T15:02:11Z",
    "transport": "api",
    "step": "browse",
    "presence": {
      "kind": "agent",
      "trust": "credentialed",
      "confidence": null,
      "risk_level": "no_risk",
      "agent_platform": "Example Agent",
      "sources": [
        "kya"
      ],
      "is_detection_available": false
    },
    "evidence": [
      {
        "source": "kya",
        "kind": "agent",
        "trust": "credentialed",
        "detail": {
          "source": "kya",
          "credential_id": "urn:uuid:2b1e4c6a-8f3d-4e2b-9a71-5c0d3e8f1a22",
          "verification_level": 3,
          "cnf_thumbprint": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
          "arrived_by": "presentation"
        }
      }
    ],
    "identity_level": "L3",
    "sdk": {
      "language": "typescript",
      "version": "1.4.0",
      "policy_schema_version": 2
    },
    "outcome": "allow",
    "enforcement_mode": "enforce",
    "rule": "everyone",
    "policy_version_id": "87e11d3c-d5c3-4ab8-a2cd-a6238b2f47fe",
    "policy_version_label": null,
    "reasons": [
      "credential_verified"
    ],
    "requirement": null,
    "expires_at": null,
    "acceptance_decision_id": "6f1d2a8e-3b4c-4d5e-8f60-718293a4b5c6",
    "visit_id": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "environment": "production",
    "agent": {
      "agent_key_thumbprint": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
      "agent_key_kid": null,
      "agent_key_source": "kya",
      "agent_platform": "Example Agent",
      "first_seen_at": "2026-05-04T14:58:02Z",
      "visit_count": 4
    },
    "credential": {
      "credential_id": "urn:uuid:2b1e4c6a-8f3d-4e2b-9a71-5c0d3e8f1a22",
      "subject": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
      "verification_level": 3,
      "verification": "verified",
      "refusal_reason": null,
      "source": "presentation",
      "audience": "https://shop.example"
    },
    "credential_verification": "verified",
    "credential_refusal_reason": null,
    "merchant_outcome": null,
    "integrity": {
      "is_policy_hash_mismatch": false
    },
    "visit": {
      "state": "ended",
      "outcome": "converted",
      "first_step": "browse",
      "last_step": "checkout",
      "started_at": "2026-05-04T15:02:11Z",
      "last_seen_at": "2026-05-04T15:06:40Z"
    }
  }
]
```

`outcome` is what the policy recommended: `allow`, `challenge`, `step_up` or `block`. `merchant_outcome` is what your site then did, when the SDK could observe it. `integrity.is_policy_hash_mismatch` is true when the SDK ran a policy that differs from the version it named.

## Count Visits by Identity

`GET /acceptance/visits/identity_funnel` counts the visits that started in the range by how far their identity got, each visit counted once, at its stage now:

| Stage | Meaning |
| - | - |
| `anonymous` | Nothing identifies the visitor |
| `declared` | It says what it is, and its network address matches the platform's published range |
| `verified_agent` | It signed its requests with a verified signature |
| `credentialed` | It presented a KYA credential that Baselayer verified |

A visit that detection only scored counts as `anonymous` here. This is separate from a visit's `funnel_stage`, which is how far it got toward a purchase.

```bash theme={null}
curl "https://api.baselayer.com/acceptance/visits/identity_funnel?start_date=2026-05-01&end_date=2026-05-07&tz=America/New_York" \
  -H "X-API-Key: $BASELAYER_API_KEY"
```

```json theme={null}
{
  "start_date": "2026-05-01",
  "end_date": "2026-05-07",
  "tz": "America/New_York",
  "stages": [
    {
      "stage": "anonymous",
      "visit_count": 412
    },
    {
      "stage": "declared",
      "visit_count": 57
    },
    {
      "stage": "verified_agent",
      "visit_count": 21
    },
    {
      "stage": "credentialed",
      "visit_count": 9
    }
  ],
  "credentialed_by_level": [
    {
      "identity_level": "L1",
      "visit_count": 0
    },
    {
      "identity_level": "L2",
      "visit_count": 2
    },
    {
      "identity_level": "L3",
      "visit_count": 7
    }
  ],
  "total_visit_count": 499
}
```

## Reading Identity Levels

A visit's `identity_level`, and the `identity_level` filter, run from `L1` to `L3`:

| Level | Meaning |
| - | - |
| `L1` | Which platform runs the agent: it declared or signed as a known platform, or presented a platform credential |
| `L2` | The same person each time, without who they are: a verified per-person identifier, or a KYA credential with a confirmed phone or email |
| `L3` | A verified person or business: only a KYA credential with a full identity match for a person, or a verified business |

A KYA credential with `verification_level` 1, 2 or 3 gives the visit `identity_level` `L1`, `L2` or `L3`. `identity_level` is null for a person, an unknown visitor or a claim nothing corroborated.


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