Repository navigation
Conversation
The SDK parity table marked Python as missing two features that the Python SDK has: - Role Derivations: permit.api.resource_roles has create_role_derivation(), delete_role_derivation() and update_role_derivation_conditions(). - Create User + Assign Roles: UserCreate takes role_assignments, which permit.api.users.create() sends with the user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parity table was compared against permit 3.0.0, the current release on PyPI, not 2.8.3. Say that 3.0.0 requires Python 3.10 or later, and link the migration guide from 2.x, pinned to the v3.0.0 tag so the link keeps pointing at the 2.x-to-3.0 guide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Cloud PDP serves the check, bulk check, user permissions, authorized users and AuthZen routes. It has no /user-tenants, no /allowed_url and no /facts forwarding, so a call to them gets a 404. The "APIs available only on an Edge PDP" list named only the all-tenants check and /local/*. Add the three missing APIs, and add user tenants and the URL mapping check to the comparison table (the table already covers the facts API as Send Consistent Updates). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PDP waits for the data update on DELETE /facts/users/{user_id}/roles
and DELETE /facts/role_assignments, the routes that
permit.api.users.unassign_role() and permit.api.role_assignments.unassign()
call when proxy_facts_via_pdp is on. The route list left them out and a
separate sentence below it named them. Move them into the list under
Role Assignments and drop the sentence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The signature table lists permit.get_user_permissions(), but the page had no Python example. Add a Python tab to the basic call and to the filter example, after the Node.js tab. Both use the async client, run with asyncio.run(), and pass the filters as keyword arguments. The result is a dict keyed by tenant or resource instance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.0.0 requires Python 3.10 or later (python_requires>=3.10). On Python 3.8 or 3.9, pip install permit quietly installs a 2.x release, so a reader on an older Python gets an SDK that the pages no longer describe. The pages said "Python 3" or named no version. - Python SDK install steps (asyncio and sync, shared by the SDK quickstarts and Connect your app): state the requirement and link the 3.0 migration guide for applications on 2.x. - Policy script and the FastAPI, Flask and Django quickstarts: the prerequisite says Python 3.10 or later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With proxy_facts_via_pdp on, the Python SDK sends list and get calls for users, tenants, role assignments, resource instances and relationship tuples to the PDP's /facts/ routes, not only writes. The Edge-PDP-only list said writes only, so a reader pointing the SDK at the Cloud PDP would expect reads to keep working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The requirement sat after the install command, so a reader on Python 3.8 or 3.9 had already installed a 2.x release before reading it. Put it in the install step's text, and keep the note about 2.x and the migration guide link after the command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The example passed the optional log setting. It now shows only the token and the PDP address; the table above still documents `log`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CONTRIBUTING.md asks for Prettier-formatted .mdx, and all 11 pages this PR changes already failed prettier --check on master. Run the repo's Prettier (2.8.8, .prettierrc.json) on those files only. Tables are realigned, trailing spaces are removed, list continuation lines lose their indent, and JS and JSON code blocks get Prettier's spacing and trailing commas. The rendered prose of every page is unchanged; only whitespace and commas inside those code blocks differ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
✅ Deploy Preview for permitio-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The following commits change this page, and it failed prettier --check on master. Run the repo's Prettier (2.8.8, .prettierrc.json) on it first, so their diffs show only content. Only the endpoint table is realigned; the rendered page is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The assign_group example sent {"group_instance_key": "group:org1"}.
The API looks the body's group up by instance key or ID among the
groups of the path group's resource type, so the type:instance form
matches no group and the request returns 404. The body now sends the
key alone, "org1". The Python SDK's end-to-end test of
groups.assign_group() checks both forms against the API.
The page also said only "org1 is assigned to support", which doesn't
say who gets whose roles. State the direction the same test confirms:
the members of the group in the path become members of the group in
the body and derive its roles, and not the other way round.
The note on path identifiers said a path takes resource_type:instance
only. It also takes the group's ID, or the instance key alone, which
names a group of the default "group" resource type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds permit.api.groups, which sends the Groups API requests the page describes. The new section sets up the page's marketing team example from Python and maps each method to its endpoint. It states: - the identifiers group_instance_key takes: the group's ID, "<type>:<key>", or the key alone for the default "group" type; - that a group's role is a resource role on one instance, reached through ReBAC derivation, so a type-only check doesn't use it; - the direction of assign_group(), and that its second argument takes the key alone or the ID; - that list() takes no filters, and that the early-access list endpoints have no SDK method. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds permit.api.tenants.create_user() and permit.get_user_tenants(). In the Python SDK examples: - create_user() creates a new user as a member of a tenant, with no role there. The API returns 409 for an existing key, so it can't add an existing user to another tenant; assign_role() does that. It always goes to the Permit API, delete_tenant_user() returns 404 for a member with no role, and add_user() is a deprecated alias. - get_user_tenants() asks the PDP for the tenants in which the user has a tenant-level role. Resource roles and role-less membership don't count, and the Cloud PDP returns 404, which the SDK raises as a PermitConnectionError. The Cloud PDP capabilities page said /user-tenants lists the tenants a user is "associated with". It lists the tenants in which the user has a tenant-level role; name the Python method next to Java's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds a context argument to permit.get_user_permissions(), so Python can request ABAC permissions with enable_abac_user_permissions. The page said the Python function takes no context and sent Python callers to the PDP API. - Add context=None to the Python signature, with the version it needs. - Say Node.js has no context argument, and that the Python SDK merges context over the context store's base context, as check() does; a call without context sends none. - Add a Python tab to the ABAC example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds list_detailed() to permit.api.role_assignments, resource_instances and relationship_tuples. Each takes the filters of list() as keyword arguments and returns one page with the total count. The Python SDK examples now show the role assignment call and list what each method's results add over list(): the role, user, tenant and resource instance as objects; an instance's relationships; and a tuple's subject, relation, object and tenant details. They also say that through the PDP (proxy_facts_via_pdp) only the last value of a list filter is forwarded, and that resource_instances.list(detailed_key=...) is deprecated in favour of list_detailed(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 adds permit.api.pdps.refresh(), which makes every PDP of the environment fetch all of its data from Permit again instead of at its next periodic update. Add a section with the call, and say that it returns once the refresh is triggered, not finished; that reason is optional and at most 512 characters; what the result holds; and the API key it needs (403 for a read-only key, 404 for an environment with no PDP configuration). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In permit 3.1 a client keeps one connection pool for the Permit API and one for the PDP (per event loop on the asyncio client) and reuses them. Add a section to the Python SDK examples that says closing is optional, and shows async with Permit(...) and close() on both clients. It covers what close() does, that the client stays usable after it, loops you drive yourself, requests in flight, the blocking client's background thread, and the client wait_for_sync() yields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The next commit changes this page, and it failed prettier --check on master. Run the repo's Prettier (2.8.8, .prettierrc.json) on it first, so that commit's diff shows only content. Tables are realigned and the JSON example is reindented to two spaces; the rendered prose is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1 changes what the Python SDK does around routes only a container PDP serves, and around proxy facts: - The Cloud PDP's 404 for /user-tenants, /local/* and /facts/* is raised as an error that names the route and says it needs a container PDP (PermitConnectionError from get_user_tenants(), PermitApiError from pdp_api and the facts calls). Say so on the Cloud PDP capabilities, list role assignments and Send Consistent Updates pages. - A client created with proxy_facts_via_pdp=True and the Cloud PDP address issues a UserWarning. - A timeout of 0 (facts_sync_timeout=0 or wait_for_sync(timeout=0)) is sent as X-Wait-Timeout, so the PDP answers without waiting. Earlier releases sent no header for 0, and the PDP waited its default. The Send Consistent Updates page listed the routes the PDP waits on, but not which Python methods send them. List those methods, name writes that return before the PDP has the change, and say that tenants.create_user() always goes to the Permit API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client table described log as enable and level only. Add a
section that shows log={...} for permit 3.1 or later and describes
each setting: enable, level (the SDK drops its records below it; an
unknown name logs a warning and falls back to info), label (the
bracketed prefix), and json, which isn't applied. It says the SDK adds
no loguru sink, logs HTTP requests at debug, and that the settings are
process-wide, and it gives the loguru serialized-sink recipe for JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a section to the Python SDK examples with what permit 4.0 will remove and what to use instead: pydantic 1 support, the flat permit.api methods, tenants.add_user() (an alias of create_user()), the detailed_key argument of resource_instances.list(), and PermitException. It says that in permit 3.1 PermitException warns only where code imports or reads it, including a star import, and how to make Python show DeprecationWarning outside __main__. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.1.0 adds permit.api.groups, permit.get_user_tenants() and the context argument of permit.get_user_permissions(), which requests ABAC permissions with enable_abac_user_permissions. - Mark Python supported in the Groups, Get User Tenant and Get User Permission (ABAC) rows, and say those cells need 3.1.0 or later. The Groups cell is full support: the SDK covers every Groups endpoint but the early-access list endpoints, as the Groups API page says. - List Python 3.1.0 in the package version table. - Split the "Java only" row of the method table: give Get User Permission (ABAC) and Get User Tenant their own rows with the Python calls, and keep Check URL as Java only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page's description and intro listed only the topics it had before. Name the log, close, list, PDP refresh and deprecation sections, and link the Python section of the Groups API page from the intro and the next steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit.api.sync_user() is deprecated in permit 3.x and warns with its replacement, permit.api.users.sync(), which takes the same user object. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The /register endpoint returned the UserRead and RoleAssignmentRead objects the SDK gives back. They are pydantic v1 models, which current FastAPI releases refuse to serialize, so the endpoint answered 500. Return them with .dict(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Flask routes ran each call of the asyncio client with asyncio.run(), which starts a new event loop, and with it new HTTP connections, for every call. permit.sync.Permit is the client for synchronous code, so the routes call it directly. The SDK's models always have .dict(), so the hasattr() fallbacks go too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SDK's models are pydantic v1 models under either pydantic major, so .dict() is always there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit.sync.Permit returns the results of the API calls directly, and since permit 3.0 the result of authorized_users() too. Two pages said every Python call must be awaited. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The expected output showed "with id", which the Go sample prints. The Python sample prints "with key". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In permit 3.1, tenants.create_user() always goes to the Permit API, even with proxy_facts_via_pdp on, so the Cloud PDP capabilities page no longer says every tenants call goes to the PDP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wait_for_sync(timeout, policy) takes a timeout policy as Node.js's waitForSync() does; the page named only the Node.js one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Python example had lost its indentation, so it did not parse. Indent it as in the example repository, with its except clause, and say that its permit.check() runs on the blocking client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The login route examples had a stray space before every line after the first, so they did not parse, and the client example said the SDK must be "at least version 1.1.3rc1". Indent the routes, and create the client from `from permit import Permit` as the other Python pages do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two quickstart ABAC examples mixed tabs and spaces, which renders out of line and which PEP 8 rules out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The error handling and check examples created the client with Permit(...), which fails at runtime and which type checkers reject now that the package is typed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit 3.0 ships type information, and mypy inferred the unannotated lists as dicts of Collection[str], so the script's resources.get() and roles.get() calls failed type checking. Annotate the lists as list[dict[str, Any]]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The close section said connection reuse is new in 3.1, but not that close() and the context manager are too: permit 3.0's client has neither, so async with Permit() fails on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prettier split the long links of the What's next box onto three lines. MDX then wraps the link text in a paragraph, so the links rendered as spaced-out blocks instead of a plain list. The lists are back on one line each, with a prettier-ignore comment so Prettier leaves them so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three install steps of the Access Request MCP implementation guide opened each code fence at the step's indentation but put the commands and the closing fence at column 0. Prettier reads that as unclosed fences and corrupts them. The commands and closing fences now share the opening fence's indentation, which MDX strips, so the page renders the same commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prettier aligns the tables, adds blank lines in the timeline steps, and puts the args of the Claude Desktop config on one line. The rendered page is otherwise the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deprecated APIs table said every row works and warns in permit 3.x. tenants.add_user() and create_user() are new in permit 3.1, and permit 3.0 doesn't warn for the detailed_key argument. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deprecated APIs section described the PermitException warning and its filter as permit 3.x behaviour. permit 3.0 warns when `import permit` runs, with the message "Use PermitError instead", so the page's filter doesn't match it. The section now marks the warning and its filter as permit 3.1 or later, and gives the permit 3.0 message and filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Formatting the partial with Prettier also reformatted its Node.js (Express) example, which this Python sweep leaves alone. The example is back to its original text, with a prettier-ignore comment before it so the file stays Prettier-clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page said check() and filter_objects() raise PermitConnectionError whenever the PDP doesn't answer. They raise it when the PDP can't be reached or answers with an error status. A request that times out raises TimeoutError, which the page's handlers don't catch. The paragraph now says so and shows how to catch both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The example repository's lock file pins permit 2.7.2. The versions note now says so, and that the page's Permit SDK calls work the same in permit 3.x, as the LangChain page does for its example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Flask quick-start uses the blocking client, but its install step linked the asyncio quickstart. The step now names the client the tutorial uses and links the sync quickstart, as the FastAPI and Django pages do for theirs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Takes the same argument" could read as the four arguments of the v1 sample above it. The sentence now names the single dict or UserCreate argument that permit.api.users.sync() takes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The file failed prettier --check on master too. Prettier indents it with two spaces and writes the escaped slashes in the two Java snippets as plain slashes. The parsed JSON is the same, so every snippet renders the same text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 4, 2026
GEO draft: answer-first blocks, facts tables, code and FAQ for concepts and policy-basics pages
#664
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear issues
Why
permit 3.0.0 is the current Python SDK release on PyPI, and 3.1.0 is next. The docs still described 2.8.3 in several places, and Python snippets across the site used calls that 3.x deprecates or that did not work as written:
permit.api.sync_user()./registerreturned the SDK's models directly, which current FastAPI answers with 500.POST /tenants/{tenant}/users, which answers 409 for a key that already exists.PermitApiError, whichcheck()andfilter_objects()never raise.What changed
permit 3.0
v3.0.0tag.POST /user-tenants,POST /allowed_urland the Local Facts API. It says that withproxy_facts_via_pdp=Truethe Python SDK sends facts reads to the PDP as well as writes, and thattenants.create_user()always goes to the Permit API.DELETE /facts/users/{user_id}/rolesandDELETE /facts/role_assignments.pip install permit, and note that pip on Python 3.8 or 3.9 installs 2.x. The policy-script page, the FastAPI, Flask and Django quick-starts and the AWS API Gateway page require Python 3.10 or later.permit 3.1 (each marked "permit 3.1 or later")
permit.api.groups. The group-to-group cURL example sends the other group's key alone, because thetype:instanceform returns 404.tenants.create_user(),permit.get_user_tenants(),list_detailed(),permit.api.pdps.refresh(), connection reuse withclose()andasync with/with, and thelogsettings. A table lists the APIs that permit 4.0 removes. It says which deprecations need 3.1, and gives thePermitExceptionwarning text and filter for 3.1 and for 3.0.contextargument.Snippets aligned with 3.x
permit.api.users.sync()replaces the flatpermit.api.sync_user(). The v2 guide says the flat method takes the same single argument, is deprecated, and is removed in permit 4.0.user.dict()androle_assignment.dict(). Flask uses the blocking client (permit.sync.Permit) and links the sync quickstart. Django and Flask call.dict()directly.managerrole in themarketingtenant withPOST /users/{id}/roles, as the Node.js tab does. The tenant users endpoint is gone from the page's endpoint table.PermitConnectionError. A paragraph says when it is raised, says a timeout raisesTimeoutError, and shows how to catch both. The versions note says the example's lock file installs permit 2.7.2.langchain-permit0.1.4 requires permit 2.x, so the tutorial stays on 2.x. It links "Staying on 2.x for now" in the migration guide.x-user-keyheader. The page names the blocking client.src/components/elements/codeBlock.json): the Python login routes parse, and the client snippet usesfrom permit import Permit.await. local-facts-uploader mentions thepolicyargument ofwait_for_sync(). data-filtering says what the Python sample prints. The policy script's lists are typed. The ABAC check examples indent with spaces. TwoPermit(...)placeholders get real arguments. The Flask example in the auth partial has its indentation back.Formatting
.prettierrc.json) runs on every changed page and oncodeBlock.json. Tables are realigned, and whitespace changes inside some JSON, JS and Python code blocks.{/* prettier-ignore */}, so MDX does not wrap the link text in<p>.{/* prettier-ignore */}.Behaviour changes
Most changes are to docs content. One data file under
src/changes:src/components/elements/codeBlock.json, which holds the Elements API snippets. These changes alter what the documented code does:/registeranswers 200 with the user and the role assignment as JSON, instead of 500.permit.sync.Permitand calls it directly, instead of wrapping each asyncio call inasyncio.run().POST /v2/facts/{proj}/{env}/users/user%7C123456789/roleswith{"role": "manager", "tenant": "marketing"}.x-user-keyis missing, and the layer contains Linux wheels for the Lambda runtime.SecurityError.How it was tested
npm run buildon the final head: exit 0.src/components/code-block/CodeBlock.js, which this PR does not change.prettier --checkpasses on all 24 changed files.codeBlock.json.permit.apimethods,tenants.add_user(),detailed_key=andPermitExceptionfinds only the v1 sample.sync_user()on two pages, a droppedawait,tenants.add_user(), the Flask routes on the asyncio client,list(detailed=True), and stray leading spaces in an Elements snippet.-W error::DeprecationWarning:import permitfails with "Use PermitError instead" and passes withignore:Use PermitError instead:DeprecationWarning. On the 3.1 source, the page's 3.1 filter silencesfrom permit import PermitException.pdp_timeout=1),check()andfilter_objects()raiseTimeoutError. A refused connection raisesPermitConnectionErroron 3.0.0 and on 2.7.2./registeranswers 200 with the expected JSON. Before the change it answered 500.langchain-permit==0.1.4together withpermit>=3.0.0,<4. On its own it resolves to permit 2.8.3.proxy_facts_via_pdp=True, the facts reads sentGET /facts/...to the PDP. With the option off, they went to/v2/facts/...on the API.<p>.Owner actions before merge
v3.0.0tag, which never moves;mainwould pick up later edits.src/sdks/walkthroughs/authz-queries/getAuthorizedUsers/example.pyis missing anawait.src/sdks/walkthroughs/sync-first-user/add-user-to-tenant/example.bashsendsPOST /tenants/{tenant}/users, which answers 409 for an existing user.bearerkey incodeBlock.jsontuple-unpackslogin_as().docs/tutorials/_connecting_to_authentication.mdxis not imported by any page.POST /users/{id}/rolesanswers 409 for a role the user already has. This decides whether the sync quickstart's "safe to restart" sentence needs atry/except.🤖 Generated with Claude Code