Manage requests
Use client.requests in the Atlan Python SDK to programmatically list, create, approve, and reject requests from the (classic) Requests module—the requests you see under Governance Center → Requests.
Atlan has two coexisting approval systems. This page covers the classic Requests module. If your tenant has the Governance Workflows and Inbox feature enabled (Admin → Labs), new requests are routed through governance workflows and appear in the Inbox instead—see Manage governance workflow approvals for those. A tenant can have requests in both systems at the same time.
Requests can only be approved or rejected by an eligible approver, and a token authenticates as its own identity, not as the human who created it. An API key is a service-account identity that isn't an eligible approver, so approve and reject calls fail with user unauthorized to perform action (error 1000) even when the requests are visible. Authenticate instead with OAuth client credentials whose identity is an eligible approver, such as an admin; see set up the SDK for how to initialize the client. Listing works with any token, but list_actionable() returns 0 for an identity that can't approve.
List requests
To list requests, use typed filters—no knowledge of the underlying filter grammar is needed:
- Python
- Raw REST API
from pyatlan.client.atlan import AtlanClient
from pyatlan.model.enums import AtlanRequestStatus, AtlanRequestType
client = AtlanClient()
response = client.requests.list( # (1)
status=AtlanRequestStatus.ACTIVE, # (2)
request_type=AtlanRequestType.ATTRIBUTE, # (3)
)
for request in response: # (4)
print(request.id, request.destination_qualified_name, request.destination_value)
list()returns requests visible to your token's identity. Uselist_actionable()with the same arguments to see only the requests your identity can approve or reject.- Filter by status:
ACTIVE,APPROVED, orREJECTED. All typed filters combine with AND. - Filter by type:
ATTRIBUTE,ATLAN_TAG,TERM_LINK,CUSTOM_METADATA, and others. You can also filter bydestination_guid,destination_qualified_name,entity_type, andcreated_by. - Iterating the response lazily pages through all matches. You don't need to manage
offset/limityourself. Useresponse.recordsif you only want the current page.
curl -s "https://tenant.atlan.com/api/service/requests?limit=20&offset=0&filter=%7B%22%24and%22%3A%5B%7B%22isDuplicate%22%3Afalse%7D%2C%7B%22status%22%3A%7B%22%24in%22%3A%5B%22active%22%5D%7D%7D%5D%7D" \
-H "Authorization: Bearer $API_TOKEN"
The filter query parameter uses an operator grammar: status must be wrapped in $in (for example {"status": {"$in": ["active"]}}). A plain equality filter such as {"status": "active"} is silently ignored. You receive unfiltered results with no error. The Python SDK's typed filters emit the correct grammar for you.
Create request
To raise a request programmatically (for example, suggesting an attribute change for someone else to approve):
- Python
from pyatlan.model.atlan_request import AttributeRequest
request = AttributeRequest.creator( # (1)
destination_guid="b4113341-251b-4adc-81fb-2420501c30e6", # (2)
destination_qualified_name="default/snowflake/1657037873/DB/SCHEMA/TABLE",
destination_attribute="userDescription", # (3)
destination_value="Proposed description",
entity_type="Table",
)
created = client.requests.create(request)
print(created.id, created.status) # (4)
- Always use the
creator()helper—it populates all fields the API requires on the wire (requestType,approvalType,sourceType). - Both the GUID and
qualifiedNameof the target asset are required. - The attribute to change, in its API (camelCase) form.
- The created request comes back with its server-assigned
idandstatusofactive.
Approve or reject requests
Approving a request executes the requested change—for example, an approved attribute request writes the proposed value onto the asset, and an approved tag request attaches the tag. Rejection discards the change. There is no separate "apply" step.
Approving and rejecting run as your token's identity, so authenticate with OAuth client credentials for an eligible approver. An API key is a service account and returns user unauthorized to perform action.
- Python
- Raw REST API
import os
from pyatlan.client.atlan import AtlanClient
# Approving and rejecting must run as an eligible approver, so authenticate
# with OAuth client credentials (an API key won't work). See "Set up the SDK".
client = AtlanClient(
base_url=os.getenv("ATLAN_BASE_URL"),
oauth_client_id=os.getenv("ATLAN_OAUTH_CLIENT_ID"),
oauth_client_secret=os.getenv("ATLAN_OAUTH_CLIENT_SECRET"),
)
assert client.requests.approve( # (1)
guid="ee32c52b-fb1e-4c6a-905d-1ebefddfbed3",
message="Looks good!", # (2)
)
assert client.requests.reject(
guid="45cd6f9b-9c6f-4b21-8d34-183f7ddccf1e",
message="Duplicate of an earlier change",
)
- Returns
Truewhen the action succeeds. Actioning a request that's no longeractive(already approved or rejected—for example, by someone in the UI) raises anInvalidRequestError. - The message is optional and appears in the request's history.
curl -s -X POST "https://tenant.atlan.com/api/service/requests/{request-id}/action" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "approved", "message": "Looks good!"}'
Bulk-approve all active requests
Combining listing and actioning gives you a bulk-approval script:
- Python
from pyatlan.client.atlan import AtlanClient
from pyatlan.model.enums import AtlanRequestStatus
client = AtlanClient()
for request in client.requests.list(status=AtlanRequestStatus.ACTIVE): # (1)
ok = client.requests.approve(guid=request.id, message="Bulk approved")
print(f"{request.id}: {'approved' if ok else 'FAILED'}")
- Iteration pages through all active requests, not just the first page.
Good to know
- Retrieving one request:
client.requests.get(guid)returns the request, orNoneif it doesn't exist. - Who approved/rejected: the
approved_by/rejected_byfields vary in shape across Atlan versions—a username string, a list of usernames, or a list of approver-detail objects. Handle them as untyped values. - Async client: every method here is also available on
AsyncAtlanClient. Useawait client.requests.list(...), withasync forpagination. totalRecordvsfilterRecord: in list responses,total_recordcounts all requests whilefilter_recordcounts those matching your filter.