Hi everyone Jumping in here for some guidance fro...
# permit-saas
a
Hi everyone Jumping in here for some guidance from the team and anyone who’s worked with similar patterns. Context: we’re currently integrating with Permit.io for authorization, and Friday our API key was temporarily blocked. From the follow-up we received, this was due to a high number of schema mutations (~25,000 in under 4 hours), caused by how we were handling role assignments to groups (which triggered policy/schema changes rather than simple fact updates). We’ve since been advised to move toward fact-based endpoints (
/fact
) instead of schema-based ones, which makes sense conceptually. That said, we’re trying to better understand the limits and expected usage patterns so we don’t run into this again. A few things we’d really appreciate clarity on: • Even though the documented limit for schema endpoints is 1,000 req/min, we still hit a block - are there additional practical limits or safeguards around schema mutations we should be aware of? • If we switch to fact-based endpoints, what level of throughput or usage is considered “safe” before hitting similar limits? • Are there recommended patterns or best practices for handling high-frequency updates (e.g. lots of role/permission changes)? • Has anyone in the community dealt with similar scaling concerns, and how did you approach it? Our main goal is to make sure we design this correctly going forward and avoid unexpected disruptions, especially since our use case may involve fairly heavy API usage.
a
Hi @Andy, I'm an AI assistant for Permit.io. I'll analyze available sources and get back to you shortly.
Based on the documentation, Permit.io's API has specific rate limits you should be aware of: The overall API is limited to ​*5,000 requests per minute*​. For fact requests (
/v2/facts/*
) , the limit is ​*3,000 requests per minute*​. For schema requests (
/v2/schema/*
) , the limit is ​*1,000 requests per minute*​. For bulk requests, the limit is ​*100 requests per 10 minutes*​. (1) Regarding your schema mutation issue, you're correct that moving to fact-based endpoints is the right approach. Schema endpoints should be used for defining your authorization model (resources, roles, actions) , while fact endpoints handle runtime data (users, tenants, role assignments, relationship tuples) . For high-frequency updates with facts, you have a couple of options: ​*Bulk operations*​: If you need to perform multiple permission checks at once, you can use the
bulkCheck()
function to validate multiple permission requests in a single call rather than making individual calls. (2) ​*Consistent updates*​: When creating or updating data that you need to immediately use for permission checks, you can enable
proxy_facts_via_pdp
in your SDK configuration. This routes data changes through the PDP and waits for them to be applied before responding, ensuring immediate consistency. (3) For example in Python:
Copy code
permit = Permit(
    token="<your-api-key>",
    pdp="<http://localhost:7766>",
    proxy_facts_via_pdp=True
)
The key architectural shift is: use schema endpoints sparingly (only when your authorization model changes) , and use fact endpoints for all runtime data operations like role assignments, user creation, and relationship management. This keeps you well within the 3,000 req/min limit for facts versus the 1,000 req/min for schema. To help investigate your specific case further, could you share: 1. Which Workspace / Project / Env you are using 2. What calls you are performing (as SDK code or CURL) 3. Which PDP version you are running 4. Relevant logs from your PDP This will help the engineering team provide more specific guidance for your use case.
To learn more, see the sources I considered: [[1] Using the API](https://docs.permit.io/api/api-with-cli) [[2] Bulk Check](https://docs.permit.io/how-to/enforce-permissions/bulk-check) [[3] Send Consistent Updates (Read-Your-Own-Writes)](https://docs.permit.io/how-to/manage-data/local-facts-uploader)
o
Hi Andy, The AI gave a good answer. I’d recommend exploring a different policy model to best approach your need here - you can share more details of your use case and we can advise on suitable modeling options
a
hi Or, I am still keen to hear about why the API key was blocked even though we were below the 1000 req / min limit
o
Because you created more system pressure than we were willing to accept, and exceeded our fair use policy - even if not explicitly listed in our docs. Additional limits set for schema APIs were not enabled and have been now restored.
a
Got it. About our use case: We're building a document management system that syncs permissions from cloud storage providers (Google Drive, Dropbox, Box) via Merge.dev. Here's the challenge we're solving: - External user identities: Files in Google Drive are shared with user IDs that don't exist in our system yet (e.g., alice@example.com has access before she signs up for our app) - Large permission sets: A single folder might be shared with 50-100+ people across an organization - Real-time sync: When permissions change in the cloud provider, we need to reflect them immediately - Late-binding users: When someone eventually signs up and links their Google account, they should instantly see all documents they already had access to Current Implementation: We're using Permit.io Groups to represent these external identities: 1. Create a group per external user: merge_dev_id:mdi-{externalUserId} (~10,000+ groups) 2. Grant group access to resources: assignGroupResourceRole() to give the group permission to documents 3. User joins group on signup: When a real user links their cloud account, we call assignUserToGroup() to connect them to the appropriate group This approach works functionally - users get access correctly - but we now understand it's causing schema changes. Questions for Alternative Modeling: 1. Fact-based approach: Should we model group→resource permissions differently? We're unclear on when to use /schema vs /fact endpoints for group-based access. 2. Rate limits: What's a reasonable mutation rate for production use? Our full sync might touch 5,000-10,000 permission assignments. 3. Architecture guidance: Given our "external identity groups + late-binding users" pattern, is there a better way to model this in Permit.io? We're open to restructuring if there's a more scalable approach. 4. ReBAC: Could relationship-based access control be a better fit? We need to support: user → (member of) → group → (has access to) → document chains
o
This should work well with ReBAC with direct usage of resource instances without the Groups feature, simply creating instances and relationship tuples dynamically (both facts). The groups feature implicitly creates new derivations/relationships for you, here you know the derivations (From folder to folder and folder to file, no need to change them on the fly) - the groups feature is more focused on ease of use than scale. Obviously the G-Drive example is a very good match https://docs.permit.io/modeling/google-drive/ (You’d note it doesn’t use Groups) For rate limits- the limits described in the docs represent fact changes well.
a
thanks, that sounds good. I will give it a try!
💜 1
hi @Or Weis Thanks again for the guidance on moving away from Groups toward a facts-based ReBAC model. I took some time to implement the approach you suggested and wanted to follow up with what I found, as I’ve hit a limitation that I’m not sure how to resolve within the current model. Summary of what we tried Based on your recommendation, we attempted to: • Stop using Groups entirely • Model access using resource instances + relationship tuples (facts) • Use derived roles for inheritance where needed (e.g. folder → file) Our goal was to replace our current pattern:
Copy code
user → group (external identity) → document
with a more direct ReBAC approach. Issue encountered Our use case requires late binding between internal users and external identities (e.g. Google Drive users that may not exist in our system yet). Concretely: • We sync permissions like: • external_identity (mdi-123) → viewer → document • • When a user signs up, we want: • user:real-123 → inherits access from external_identity:mdi-123 • However, we ran into the following constraints: 1. 1. We cannot create relations pointing to the built-in
user
type (attempts return 404) 2. 2. Derived roles appear to only support resource → resource propagation (as shown in the Google Drive example), not subject → subject → resource 3. 3. The official Google Drive example only models:
Copy code
user → role → resource
resource → resource (hierarchy)
and does not cover identity mapping or inheritance across subjects Because of this, we were unable to model:
Copy code
user → external_identity → document
purely with facts + derived roles. What we tried as alternatives • Attempted to model
member_of
-style relations → blocked by user relation limitation • Attempted custom Rego to resolve identity inheritance → technically possible, but seems to bypass the intended ReBAC model and complicates evaluation Clarification needed Given these constraints, we’re trying to understand the intended pattern for this type of use case. Specifically: Is it correct that Permit’s ReBAC model does not support subject-to-subject inheritance (e.g. user inheriting from external identity) without using Groups? 1. If so, what is the recommended way to model late-binding external identities at scale? 2. Treat external identities as users in Permit and resolve them at query time? 3. Materialize permissions onto real users during sync/signup? 4. Continue using Groups despite the scale concerns? 5. When you suggested using “resource instances + relationship tuples (facts)”, how would that approach handle identity mapping without Groups? Context Our requirements: • External identities exist before users sign up • Large permission sets (10k+ identities, 5k–10k assignments per sync) • Near real-time sync from providers • Users should immediately see pre-existing permissions on signup Our original Groups-based approach satisfies these functionally, but we’re trying to move away from it based on your guidance.
o
Cool, here’s my quick reply.
1. 1. We cannot create relations pointing to the built-in
user
type (attempts return 404)
2. 2. Derived roles appear to only support resource → resource propagation (as shown in the Google Drive example), not subject → subject → resource
3. 3. The official Google Drive example only models:
Usually this is solved by creating a resource instance to represent the user (e.g.
user-profile
), at the moment users (aka identities) are not automatically created as resource instances. You can see an example of this pattern here. @Ask Inkeep can elaborate
a
Thanks for tagging me @Or Weis. I'll look through relevant sources and get back to you shortly.
Based on your use case, the recommended approach is to ​*create resource instances to represent external identities*​ rather than using the built-in
user
type directly in relationships. (1) Solution: User Profile Pattern for Late-Binding Identities The pattern Or mentioned involves creating a ​*resource to represent user profiles*​ (e.g.,
member
or
user_profile
) , which allows you to: 1. Create resource instances for external identities ​*before*​ users sign up 2. Establish relationships between these profile instances and documents 3. Link real users to their profile instances when they sign up Implementation Approach ​*Step 1: Define Resources and Relations*​ Create a
member
resource (representing user profiles) with relations to your documents: (2)
Copy code
curl <https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/relations> \
 -X POST \
 -H "Authorization: Bearer $permit_sdk_api_key" \
 -H "Content-Type: application/json" \
 -d '{
 "key": "parent",
 "name": "Parent",
 "subject_resource": "folder"
 }'
(3) ​*Step 2: Create Resource Instances for External Identities*​ When syncing from Google Drive/Dropbox, create member instances for external identities: (4)
Copy code
await permit.api.resource_instances.create({
 key: "mdi-123",
 resource: "member",
 tenant: "default",
 attributes: {
 external_provider: "google_drive",
 external_id: "<mailto:alice@example.com|alice@example.com>"
 }
}) ;
​*Step 3: Assign Roles to External Identity Instances*​ Grant the external identity instance access to documents: (3)
Copy code
await permit.api.role_assignments.assign({
 user: "mdi-123",
 role: "viewer",
 resource_instance: "file:document123",
 tenant: "default"
}) ;
​*Step 4: Create Relationship Tuples*​ Establish relationships between resources: (4)
Copy code
await permit.api.relationship_tuples.create({
 subject: "folder:finance",
 relation: "parent",
 object: "file:2023_report",
 tenant: "default"
}) ;
​*Step 5: Link Real Users on Signup*​ When a user signs up and links their Google account, create a relationship between the real user and their member profile instance. You can assign the user the
owner
role on their member profile: (3)
Copy code
await permit.api.role_assignments.assign({
 user: "real-user-123",
 role: "owner",
 resource_instance: "member:mdi-123"
}) ;
​*Step 6: Set Up Role Derivations*​ Configure role derivations so that ownership of a member profile grants inherited permissions: (2)
Copy code
curl <https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/roles/viewer> \
 -X PATCH \
 -H "Authorization: Bearer $permit_sdk_api_key" \
 -H "Content-Type: application/json" \
 -d '{
 "granted_to": {
 "users_with_role": [
 {
 "linked_by_relation": "owner",
 "on_resource": "member",
 "role": "viewer"
 }
 ]
 }
 }'
(3) Key Benefits of This Approach • ​*Fact-based operations*​: All instance creation and role assignments use
/v2/facts/*
endpoints (3,000 req/min limit) (4) • ​*No schema mutations*​: Resource and relation definitions are set once; everything else is runtime data (2) • ​*Late binding*​: External identities exist as resource instances before users sign up (1)
Feel free to tag me with additional questions. I also appreciate feedback, it helps me improve. 👍 button 👎 button
a
Thanks again for your guidance earlier - I was able to successfully implement the first part of our setup using the User Profile pattern: •
user → user profile (external identity) → document
• Derived roles now correctly propagate document permissions to the user once they link their external identity This works well for our late-binding users scenario. We now have a new use case and want to check the recommended approach: • We want to model groups of external identities. • Desired access chain:
Copy code
user → external identity → group of external identities → document
• Goal: A user linked to an external identity should inherit access if any identity in the group has access to the document. My questions: 1. Is it possible to model this multi-layer chain purely with derived roles, or does Permit only support a single resource → resource propagation? 2. If derived roles don’t support this, is the recommended approach to: 3. Flatten permissions (assign each identity in the group directly to the document), or 4. Use a custom Rego policy to evaluate group membership dynamically? 5. Are there any other patterns in Permit that support this type of “intermediate resource layer” while keeping late-binding users and facts-based assignments? @Ask Inkeep @Or Weis
a
Thanks for tagging me @Andy. I'll look through relevant sources and get back to you shortly.
Yes, you can model multi-layer chains with derived roles in Permit.io's ReBAC model. The key is understanding that ​*role derivations can be chained through multiple resource relationships*​. (1) ​*Modeling the Multi-Layer Chain*​ Your desired access chain
user → external identity → group → document
can be implemented using ​*relationship tuples and role derivations*​ at each layer. (1) (2) Here's the recommended approach: ​*Step 1: Define the Resource Relations*​ Create relations between your resources to establish the hierarchy: (1)
Copy code
# Identity → Group relation
curl <https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/identity/relations> \
  -X POST \
  -H "Authorization: Bearer $permit_sdk_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "member_of",
    "name": "Member Of",
    "subject_resource": "identity_group"
  }'
Copy code
# Group → Document relation
curl <https://api.permit.io/v2/schema/$permit_project/$permit_env/resources/file/relations> \
  -X POST \
  -H "Authorization: Bearer $permit_sdk_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "shared_with",
    "name": "Shared With",
    "subject_resource": "identity_group"
  }'
​*Step 2: Configure Role Derivations*​ Role derivations allow permissions to propagate through the relationship chain. You can configure derivations so that: 1. A user with
owner
role on an
identity
inherits roles from that identity 2. An
identity
with
member
role on an
identity_group
inherits roles from that group 3. An
identity_group
with
viewer
role on a
file
grants access to the document The derivation mechanism works by following the relationships you've defined. (1) (2) ​*Step 3: Create Resource Instances and Relationships*​ When syncing permissions, create the relationship tuples as facts:
Copy code
# Link identity to group
await permit.api.relationship_tuples.create({
  subject: "identity_group:engineering",
  relation: "member_of",
  object: "identity:mdi-123",
  tenant: "default"
}) ;
(2)
Copy code
# Grant group access to document
await permit.api.role_assignments.assign({
  user: "identity_group:engineering",
  role: "viewer",
  resource_instance: "file:document123",
  tenant: "default"
}) ;
​*Answering Your Specific Questions*​ 1. ​*Multi-layer chains with derived roles*​: Yes, Permit supports chaining multiple resource relationships through role derivations. The system evaluates the entire relationship graph to determine permissions. (1) (2) 2. ​*Alternative approaches*​: While flattening permissions or custom Rego are options, the ​*recommended pattern is to use relationship tuples with role derivations*​ as this keeps everything fact-based and avoids schema mutations. (1) 3. ​*Intermediate resource layers*​: The relationship-based model inherently supports intermediate layers. Each resource in the chain (identity → group → document) is a resource instance with relationships defined as facts.
Feel free to tag me with additional questions. I also appreciate feedback, it helps me improve. 👍 button 👎 button
Thanks for the feedback, glad I was helpful.