
## Manage requests

URL: https://docs.atlan.com/product/capabilities/build-apps/sdks/python/how-tos/manage-requests

> List, create, approve, and reject Metadata Inbox requests programmatically using the Atlan Python SDK (pyatlan)—including bulk approval scripts.

# 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**.

:::info[Two request systems exist in Atlan]
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](https://docs.atlan.com/llms/platform/python/manage-governance-workflow-approvals/llms.txt) for those. A tenant can have requests in both systems at the same time.
:::

:::warning[Use OAuth client credentials to approve or reject]
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](https://docs.atlan.com/llms/platform/python/set-up-sdk/llms.txt) 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

```python showLineNumbers title="List active requests"
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)
```

1. `list()` returns requests visible to your token's identity. Use `list_actionable()` with the same arguments to see only the requests your identity can approve or reject.
2. Filter by status: `ACTIVE`, `APPROVED`, or `REJECTED`. All typed filters combine with AND.
3. Filter by type: `ATTRIBUTE`, `ATLAN_TAG`, `TERM_LINK`, `CUSTOM_METADATA`, and others. You can also filter by `destination_guid`, `destination_qualified_name`, `entity_type`, and `created_by`.
4. Iterating the response **lazily pages through all matches**. You don't need to manage `offset`/`limit` yourself. Use `response.records` if you only want the current page.

### Raw REST API

```shell showLineNumbers title="List active requests"
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"
```

:::warning[Filter grammar is operator-based]
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

```python showLineNumbers title="Create an attribute change request"
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)
```

1. Always use the `creator()` helper—it populates all fields the API requires on the wire (`requestType`, `approvalType`, `sourceType`).
2. Both the GUID and `qualifiedName` of the target asset are required.
3. The attribute to change, in its API (camelCase) form.
4. The created request comes back with its server-assigned `id` and `status` of `active`.

## Approve or reject requests

:::warning[Approval applies the change immediately]
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](https://docs.atlan.com/llms/platform/python/set-up-sdk/llms.txt) for an eligible approver. An API key is a service account and returns `user unauthorized to perform action`.

### Python

```python showLineNumbers title="Approve and reject requests"

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",
)
```

1. Returns `True` when the action succeeds. Actioning a request that's no longer `active` (already approved or rejected—for example, by someone in the UI) raises an `InvalidRequestError`.
2. The message is optional and appears in the request's history.

### Raw REST API

```shell showLineNumbers title="Approve a request"
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

```python showLineNumbers title="Approve every active request"
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'}")
```

1. Iteration pages through **all** active requests, not just the first page.

## Good to know

- **Retrieving one request:** `client.requests.get(guid)` returns the request, or `None` if it doesn't exist.
- **Who approved/rejected:** the `approved_by` / `rejected_by` fields 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`. Use `await client.requests.list(...)`, with `async for` pagination.
- **`totalRecord` vs `filterRecord`:** in list responses, `total_record` counts all requests while `filter_record` counts those matching your filter.

---
