Skip to main content

Send consistent updates through the PDP

Route data writes, such as creating a user or assigning a role, through your policy decision point (PDP), so a permission check right after the write sees the new data (read-your-own-writes). This page is for developers whose application creates data and checks permissions on that data in the same flow. The feature is also called proxy facts, and the PDP endpoints are the Local Facts API.

How proxy facts work

By default, the SDK sends data writes to the Permit API, and the Permit control plane syncs the change to your PDPs a short time later. A check that runs before the sync finishes can return a decision based on old data.

With proxy facts, the SDK sends the write to the PDP. The PDP forwards the write to the Permit API, and then waits until the PDP receives the data update before it responds. The PDP waits up to a timeout, and then responds according to the timeout policy.

Prerequisites

  • A PDP container, version 0.5.1 or later, that your application can reach. See Run the PDP. The timeout_policy option requires PDP version 0.8.0 or later.
  • A Permit SDK configured with your environment API key (Get your API key)

Create a user, then check permissions

Turn on proxy facts in the SDK configuration, and point the SDK at your PDP. In the following examples, replace <your-api-key> or <YOUR_API_KEY> with your environment API key.

from permit import Permit

# Initialize SDK with proxy_facts_via_pdp enabled
permit = Permit(
token="<your-api-key>",
pdp="http://localhost:7766",
proxy_facts_via_pdp=True
)

async def create_and_check():
# Create user
user = await permit.api.users.create({
"key": "user123",
"email": "user@example.com"
})

# Check for permissions right after
allowed = await permit.check(user.key, "read", "document")
print(f"Permission granted: {allowed}")

With proxy_facts_via_pdp enabled, permit.api.users.create() returns after the PDP receives the user data, so the permission check that follows sees the user.

Verify the consistent update

Run the example for a user key that doesn't exist yet, with a policy that allows the action for the user. The first permission check after the create call returns true. When a write times out with the fail timeout policy, the PDP responds with HTTP status 424.

Configuration options

OptionValuesPDP defaultDescription
Timeout0Don't wait. The PDP responds right after it forwards the write.
A positive number, such as 1010Wait up to this number of seconds for the data update.
A negative number, such as -1Wait with no time limit.
Timeout policyignoreignoreWhen the timeout passes, respond with the result of the write.
failWhen the timeout passes, respond with HTTP status 424 Failed Dependency. Requires PDP 0.8.0 or later.

Set the options at three levels. A level lower in the list overrides the levels above it:

  1. PDP environment variables, the defaults for every request
  2. SDK configuration, for every write from the SDK client
  3. Per-operation settings, for one write

PDP configuration

Environment variableDefaultDescription
PDP_LOCAL_FACTS_WAIT_TIMEOUT10Default timeout in seconds
PDP_LOCAL_FACTS_TIMEOUT_POLICYignoreDefault timeout policy: ignore or fail

SDK configuration

In the Python and Node.js SDKs, when you don't set a timeout or a timeout policy, the SDK sends no value and the PDP defaults apply.

# SDK-level configuration (applies to all operations)
permit = Permit(
token="<your-api-key>",
pdp="http://localhost:7766",
proxy_facts_via_pdp=True,
facts_sync_timeout=10, # Optional: Uses PDP default if not specified
facts_sync_timeout_policy="ignore" # Optional: Uses PDP default if not specified
)

# All operations will use the SDK-level settings
# user = await permit.api.users.create(user_data) # inside an async function

Operation-specific configuration

Override the timeout for one write. In Python, wait_for_sync() is a context manager that returns a client with the timeout. In Node.js, waitForSync() returns an API client with the timeout, and also accepts a timeout policy as a second argument.

# SDK initialization with proxy_facts_via_pdp enabled
permit = Permit(
token="<your-api-key>",
pdp="http://localhost:7766",
proxy_facts_via_pdp=True
)

# Override the default timeout for a specific operation
async def create_user_with_timeout(user_data: dict):
with permit.wait_for_sync(timeout=15) as p:
return await p.api.users.create(user_data)

Call the Local Facts API directly

To use proxy facts without an SDK, send facts requests to the PDP instead of the Permit API. The routes and request bodies are the same as the Permit API facts routes, without the /v2 prefix and without the project and environment in the path:

Permit APIPDP Local Facts API
https://api.permit.io/v2/facts/{proj}/{env}/...http://localhost:7766/facts/...

Set the timeout and the timeout policy for a request with headers:

POST /facts/...
Headers:
X-Wait-timeout: 10
X-Timeout-policy: ignore

HTTP header names are case-insensitive, so X-Wait-timeout and X-Wait-Timeout are the same header.

The following two requests create the same user. The first request goes through the PDP, and the second goes to the Permit API:

curl -X POST http://localhost:7766/facts/users \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"key": "user123",
"email": "user@example.com"
}'

curl -X POST https://api.permit.io/v2/facts/default/prod/users \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"key": "user123",
"email": "user@example.com"
}'

Supported APIs

The PDP waits for the data update on these routes:

# Users
POST /facts/users
PUT /facts/users/{user_id}
PATCH /facts/users/{user_id}

# Tenants
POST /facts/tenants

# Role Assignments
POST /facts/users/{user_id}/roles
POST /facts/role_assignments

# Resource Instances
POST /facts/resource_instances
PATCH /facts/resource_instances/{instance_id}

# Relationship Tuples
POST /facts/relationship_tuples

The PDP also waits on DELETE /facts/users/{user_id}/roles and DELETE /facts/role_assignments, which remove role assignments.

The PDP forwards requests to other facts routes to the Permit API without waiting for the data update.

Role assignments in user requests

The PDP doesn't wait for the role_assignments field of a user request (POST /facts/users, PUT /facts/users/{user_id}, PATCH /facts/users/{user_id}). A permission check right after the user request can miss those roles. Send each role assignment in a separate role assignment request after you create or update the user.

For API or SDK support, ask in the Permit Slack community.

Best practices

Performance considerations

  • Write latency: a write through the PDP takes longer than a write to the Permit API, because the PDP waits for the data update.
  • Unsupported routes: requests to routes the PDP doesn't wait on still pass through the PDP to the Permit API, which adds a network hop.

Deployment recommendations

Proxy facts guarantee the update only on the PDP that handled the write. Send the write and the following permission checks to the same PDP instance.

  • Recommended: deploy a centralized PDP or a PDP sidecar next to your application. The same PDP instance handles the write and the check.
  • Less reliable: with a load-balanced cluster of PDPs, a check can go to a PDP that hasn't received the data update yet.

Next steps