diff --git a/conductorone-api/common-tasks.mdx b/conductorone-api/common-tasks.mdx
new file mode 100644
index 00000000..c1420ac9
--- /dev/null
+++ b/conductorone-api/common-tasks.mdx
@@ -0,0 +1,160 @@
+---
+title: "Common multi-step API tasks"
+description: "Worked examples for API tasks that require more than one call, chaining IDs from one response into the next request."
+sidebarTitle: "Common tasks"
+---
+
+The [Endpoints reference](/api-reference) documents every C1 API endpoint individually. Some real-world tasks require chaining two or more of those endpoints together — for example, looking up an object's ID before you can act on it, or triggering a job and then checking its result. This page walks through those multi-step tasks.
+
+For single-endpoint tasks (creating an object, fetching a record by ID, and so on), go directly to the [Endpoints reference](/api-reference) — most single calls are self-explanatory there.
+
+
+A few steps below are marked **Needs verification**. These are our best reconstruction of the correct call sequence from the API reference, but haven't been confirmed against a live tenant. Confirm these with an engineer before publishing.
+
+
+## Add an entitlement to an access profile
+
+An access profile is a named bundle of requestable entitlements — in the API, this is a **request catalog**. To add an entitlement to one, you need the catalog's ID first.
+
+
+
+List or search request catalogs to find the `catalog_id` matching the access profile's display name.
+
+```bash curl
+curl -X GET "https://${TENANT}/api/v1/catalogs" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}"
+```
+
+
+```bash curl
+curl -X POST "https://${TENANT}/api/v1/catalogs/${CATALOG_ID}/requestable_entries" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "appEntitlements": [
+ { "appId": "${APP_ID}", "id": "${ENTITLEMENT_ID}" }
+ ],
+ "createRequests": false
+ }'
+```
+
+Set `createRequests` to `true` if you want C1 to automatically create access requests for the entitlement on behalf of everyone already enrolled in the access profile.
+
+
+
+## Check how many users have completed provisioning for an access profile
+
+There's no single "provisioning status" endpoint. Instead, find the entitlement(s) backing the access profile, then count tasks against them.
+
+
+
+```bash curl
+curl -X GET "https://${TENANT}/api/v1/catalogs/${CATALOG_ID}/requestable_entitlements" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}"
+```
+
+
+```bash curl
+curl -X POST "https://${TENANT}/api/v1/search/tasks" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "appEntitlementIds": ["${ENTITLEMENT_ID}"],
+ "taskTypes": ["TASK_TYPE_GRANT"],
+ "pageSize": 10
+ }'
+```
+
+Compare `taskStates: OPEN` (still provisioning) against `taskStates: CLOSED` (done) counts to get an "x of y" figure. Request a small `pageSize` (10 or fewer) — each task record is large.
+
+
+
+
+**Needs verification:** confirm the exact `taskTypes` value that represents an access-profile-driven grant, and whether a `CLOSED` task always means successful provisioning (versus closed-but-denied/failed).
+
+
+## Set an entitlement's risk level
+
+
+
+Risk levels are a shared, tenant-wide list of values — check whether the one you want already exists before creating a duplicate.
+
+```bash curl
+curl -X POST "https://${TENANT}/api/v1/attributes/risk_levels" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{ "value": "High" }'
+```
+
+The response includes the new value's `id` — save it for the next step.
+
+
+
+**Needs verification:** we could not confirm the endpoint/body that attaches an attribute value to a specific entitlement from the reference alone. Confirm with an engineer whether this is a field on the entitlement update call or a separate binding endpoint.
+
+
+
+
+## Extend or remove a grant's expiration date
+
+You need the specific grant (the binding between a user and an entitlement) before you can change its expiration — you can't do it by user or entitlement ID alone.
+
+
+
+Search grants for the entititlement to find the specific user's binding.
+
+```bash curl
+curl -X POST "https://${TENANT}/api/v1/apps/${APP_ID}/entitlements/${ENTITLEMENT_ID}/search-grants" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{ "query": "${USER_EMAIL}" }'
+```
+
+
+To set a new expiration:
+
+```bash curl
+curl -X POST "https://${TENANT}/api/v1/apps/${APP_ID}/entitlements/${ENTITLEMENT_ID}/users/${APP_USER_ID}/update-grant-duration" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{ "newDeprovisionAt": "2026-12-31T00:00:00Z" }'
+```
+
+To remove the expiration entirely (make the grant permanent), use the equivalent `remove-grant-duration` endpoint on the same binding instead.
+
+
+
+## Trigger a workflow automation via the API and confirm it ran
+
+Executing an automation and checking its result are two separate calls — the execute call only returns an execution ID, not a result.
+
+
+
+```bash curl
+curl -X POST "https://${TENANT}/api/v1/automations/${AUTOMATION_ID}/execute" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{}'
+```
+
+The response contains an `executionId`.
+
+
+```bash curl
+curl -X GET "https://${TENANT}/api/v1/automation_executions/${EXECUTION_ID}" \
+ -H "Authorization: Bearer ${ACCESS_TOKEN}"
+```
+
+Check the `state` field on the returned execution object.
+
+
+
+
+**Needs verification:** confirm the full set of `state` values an execution can return and which ones represent success versus failure.
+
+
+## Add a service principal as an app owner
+
+
+**Needs verification:** the reference documentation for `POST /api/v1/apps/{app_id}/owners/{user_id}` does not state whether `user_id` accepts a service principal's ID, or whether app ownership is restricted to human users via the API. Confirm with an engineer before documenting this as supported — if it isn't, this section should instead document the limitation.
+
diff --git a/docs.json b/docs.json
index 5942af90..92abd332 100644
--- a/docs.json
+++ b/docs.json
@@ -942,7 +942,8 @@
"pages": [
"conductorone-api/api",
"conductorone-api/authenticate",
- "conductorone-api/pagination"
+ "conductorone-api/pagination",
+ "conductorone-api/common-tasks"
]
},
{