Skip to main content
When you hold a grant to another tenant’s subject, you can see that subject’s data — but you cannot update it, because you do not own it. Refresh requests solve this: they give grantees a structured way to signal to the subject owner that new or updated identity data is needed. The owner receives a notification, submits a fresh snapshot, and marks the request as fulfilled. Both sides can track the request’s lifecycle through the API and via webhooks.

How refresh requests work

1
Grantee creates a refresh request
2
The counterparty tenant calls POST /v1/subjects/:subject_type/:subject_id/refresh-requests, supplying their requesting_tenant_id and an optional reason_code, free-text message, or list of requested_paths (JSON Pointer strings pointing to the specific attributes they need refreshed).
3
Owner receives a webhook notification
4
Tally fires a refresh_request.created event to any webhook subscriptions the subject owner has registered. The payload includes the full refresh request record so the owner can immediately see what data is being requested.
5
Owner submits a new snapshot and fulfills the request
6
The owner creates a new snapshot (via POST /v1/tenants/:tenant_id/entity-states or the propose/apply flow), then calls POST /v1/subjects/:subject_type/:subject_id/refresh-requests/:refresh_request_id/fulfill with the new snapshot_id. This links the refresh request to the snapshot that resolves it.
7
Grantee receives a fulfillment webhook
8
Tally fires a refresh_request.fulfilled event to any webhook subscriptions the requesting tenant has registered. The payload includes the resolved_snapshot_id so the grantee can immediately fetch and process the new data.

Create a refresh request

POST /v1/subjects/:subject_type/:subject_id/refresh-requests Your principal must be an active member of the requesting_tenant_id with at least the tenant_reader role, and your tenant must hold an active grant to the subject (or be the owner).
curl -X POST https://api.tally.io/v1/subjects/entity/ent_acme_001/refresh-requests \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "requesting_tenant_id": "partner-bank",
    "reason_code": "annual_review",
    "message": "Please provide an updated ownership structure for our annual KYB review.",
    "requested_paths": [
      "/attributes/relationships",
      "/attributes/registered_address"
    ],
    "expires_at": "2026-04-01T00:00:00Z"
  }'

Request body fields

FieldRequiredDescription
requesting_tenant_idYesYour tenant’s ID — must be a valid tenant with a grant (or ownership) for this subject
reason_codeNoA short machine-readable code describing why the refresh is needed (e.g. "annual_review", "adverse_media")
messageNoA human-readable message to the subject owner
requested_pathsNoArray of JSON Pointer strings specifying which attributes need refreshing
expires_atNoISO 8601 datetime after which this request should be considered stale
Do not include origin_type in your request body — it is server-derived. Tally sets it to "counterparty" if your tenant holds a grant to the subject, or "owner" if your tenant is the subject owner.
The 201 Created response wraps the new record in a refresh_request key:
{
  "refresh_request": {
    "refresh_request_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "subject": {
      "subject_type": "entity",
      "subject_id": "ent_acme_001"
    },
    "requesting_tenant_id": "partner-bank",
    "origin_type": "counterparty",
    "status": "pending",
    "reason_code": "annual_review",
    "message": "Please provide an updated ownership structure for our annual KYB review.",
    "requested_paths": ["/attributes/relationships", "/attributes/registered_address"],
    "created_at": "2026-03-10T09:00:00Z",
    "expires_at": "2026-04-01T00:00:00Z",
    "resolved_at": null,
    "resolved_snapshot_id": null,
    "resolved_snapshot_version": null
  }
}

Get a refresh request

GET /v1/subjects/:subject_type/:subject_id/refresh-requests/:refresh_request_id Both the subject owner and the requesting tenant can call this endpoint. Tally checks that your principal belongs to either the owning tenant or the requesting_tenant_id on the record.
curl https://api.tally.io/v1/subjects/entity/ent_acme_001/refresh-requests/d290f1ee-6c54-4b01-90e6-d701748f0851 \
  -H "Authorization: Bearer $TALLY_API_KEY"
The response status field follows this lifecycle:
StatusMeaning
pendingCreated and awaiting fulfillment
fulfilledThe owner has submitted a new snapshot and resolved the request
expiredThe expires_at timestamp passed without fulfillment
cancelledThe request was cancelled before fulfillment

List refresh requests

GET /v1/subjects/:subject_type/:subject_id/refresh-requests

Query parameters

ParameterDescription
requesting_tenant_idFilter to requests from a specific tenant. If omitted, returns all requests for the subject — requires owner access.
limitNumber of results per page (default 50, max 200)
cursorBase64url pagination cursor from a previous response
# Owner listing all refresh requests for a subject
curl https://api.tally.io/v1/subjects/entity/ent_acme_001/refresh-requests \
  -H "Authorization: Bearer $TALLY_API_KEY"

# Grantee listing only their own requests
curl "https://api.tally.io/v1/subjects/entity/ent_acme_001/refresh-requests?requesting_tenant_id=partner-bank" \
  -H "Authorization: Bearer $TALLY_API_KEY"
{
  "items": [
    {
      "refresh_request_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "subject": { "subject_type": "entity", "subject_id": "ent_acme_001" },
      "requesting_tenant_id": "partner-bank",
      "origin_type": "counterparty",
      "status": "pending",
      "reason_code": "annual_review",
      "message": "Please provide an updated ownership structure...",
      "requested_paths": ["/attributes/relationships"],
      "created_at": "2026-03-10T09:00:00Z",
      "expires_at": "2026-04-01T00:00:00Z",
      "resolved_at": null,
      "resolved_snapshot_id": null,
      "resolved_snapshot_version": null
    }
  ],
  "page": {
    "limit": 50,
    "next_cursor": null
  }
}

Fulfill a refresh request (as owner)

POST /v1/subjects/:subject_type/:subject_id/refresh-requests/:refresh_request_id/fulfill Once you have submitted a new snapshot, call this endpoint to link it to the pending refresh request. Your principal must be an active member of the subject’s owning tenant.
curl -X POST https://api.tally.io/v1/subjects/entity/ent_acme_001/refresh-requests/d290f1ee-6c54-4b01-90e6-d701748f0851/fulfill \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "resolved_snapshot_id": "3f0b2c2c-2e46-4b58-8c45-3b5c58f4e9b2"
  }'
On success, the response returns the updated request with "status": "fulfilled" and the resolved_snapshot_id populated:
{
  "refresh_request": {
    "refresh_request_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "subject": { "subject_type": "entity", "subject_id": "ent_acme_001" },
    "requesting_tenant_id": "partner-bank",
    "origin_type": "counterparty",
    "status": "fulfilled",
    "reason_code": "annual_review",
    "resolved_at": "2026-03-12T14:00:00Z",
    "resolved_snapshot_id": "3f0b2c2c-2e46-4b58-8c45-3b5c58f4e9b2",
    "resolved_snapshot_version": 3,
    "expires_at": "2026-04-01T00:00:00Z"
  }
}
The resolved_snapshot_id must correspond to an existing snapshot for the same subject. If the snapshot does not match the subject, or if a different snapshot has already been used to fulfill this request, Tally returns 409 Conflict.
Refresh requests expire automatically. Always check expires_at before attempting to fulfill a request. Tally will return a 409 Conflict with status set to "expired" or "cancelled" if you try to fulfill a request that is no longer in "pending" status. To handle a re-request after expiry, ask the grantee to create a new refresh request.