> ## Documentation Index
> Fetch the complete documentation index at: https://platform.doodocs.kz/llms.txt
> Use this file to discover all available pages before exploring further.

# Approve a document as a participant

> Catch the event, find your slot, and approve, reject, or send it back

An integrator does not always create documents. Often it acts on the approver's side:
a system of yours decides whether to approve an order or send it back for changes, and
it has to do that through the API.

Everything below needs the `documents:write` scope, and reading the document needs
`documents:read`.

<Warning>
  A document you can reach only through route participation **does not appear** in
  `GET /documents`: the registry lists by initiator and by permission. Events are
  therefore the only reliable entry point for this scenario. See
  [Listing documents](/en/guides/documents/list).
</Warning>

<Steps>
  <Step title="Subscribe to approval events">
    ```bash theme={null}
    curl -X POST -H "X-API-Key: $DOODOCS_API_KEY" -H "Content-Type: application/json" \
      https://app.doodocs.kz/api/developer/v1/webhook_endpoints \
      -d '{
        "url": "https://your-server/hooks/doodocs",
        "event_types": ["document.approval_started", "document.change_requested", "document.completed"]
      }'
    ```

    The event carries `{"document_id": "…"}`, which is all you need to read the
    document with your own key.
  </Step>

  <Step title="Read the document with its route">
    ```bash theme={null}
    curl -H "X-API-Key: $DOODOCS_API_KEY" \
      https://app.doodocs.kz/api/developer/v1/documents/8c2d5a91-…
    ```

    ```json theme={null}
    {
      "document": {
        "id": "8c2d5a91-…",
        "status": "DOCUMENT_STATUS_ON_APPROVAL",
        "route": {
          "is_active": true,
          "steps": [
            {
              "id": "st_1…",
              "index": 0,
              "step_type": "STEP_TYPE_APPROVAL",
              "rule": "STEP_RULE_ALL",
              "status": "STEP_STATUS_ACTIVE",
              "participants": [
                {
                  "id": "pt_9f…",
                  "employee_id": "3f2a9c7e-…",
                  "status": "PARTICIPANT_STATUS_ACTIVE"
                }
              ]
            }
          ]
        }
      }
    }
    ```
  </Step>

  <Step title="Find your slot">
    You need the participant whose `employee_id` matches the key owner's employee
    record and whose `status` is `PARTICIPANT_STATUS_ACTIVE`. That participant's `id`
    is what an action takes.

    ```python theme={null}
    def my_active_slot(document: dict, my_employee_id: str) -> str | None:
        for step in document["route"]["steps"]:
            if step["status"] != "STEP_STATUS_ACTIVE":
                continue
            for p in step["participants"]:
                if p["employee_id"] == my_employee_id and p["status"] == "PARTICIPANT_STATUS_ACTIVE":
                    return p["id"]
        return None
    ```

    <Note>
      The Developer API has no "who am I" endpoint. Store the key owner's
      `employee_id` in your integration's configuration; it stays the same while the
      owner works in the same organization.
    </Note>
  </Step>

  <Step title="Perform the action">
    ```bash theme={null}
    curl -X POST -H "X-API-Key: $DOODOCS_API_KEY" -H "Content-Type: application/json" \
      https://app.doodocs.kz/api/developer/v1/documents/8c2d5a91-…/_approve \
      -d '{ "participant_id": "pt_9f…", "comment": "Approved" }'
    ```

    | Action          | Endpoint           | Comment      | Result                             |
    | --------------- | ------------------ | ------------ | ---------------------------------- |
    | Approve         | `_approve`         | optional     | the step moves on                  |
    | Reject          | `_reject`          | **required** | `DOCUMENT_STATUS_REJECTED`         |
    | Request changes | `_request_changes` | **required** | `DOCUMENT_STATUS_CHANGE_REQUESTED` |
  </Step>
</Steps>

## Step completion rules

`rule` decides when a step closes:

* `STEP_RULE_ALL` — every participant of the step must act;
* `STEP_RULE_ANY_ONE` — one is enough, and the remaining slots become
  `PARTICIPANT_STATUS_SKIPPED`.

So do not assume your approval closes the step. Re-read the document afterwards, or
wait for `document.signing_started` or `document.completed`.

## Errors worth handling

| Situation                                         | What you get                         |
| ------------------------------------------------- | ------------------------------------ |
| The slot already acted, or the step is not active | `400`, a document state error        |
| A `participant_id` that is not yours              | `404` `DOCUMENT_NOT_FOUND`           |
| The document is not accessible to the key         | `404` `DOCUMENT_NOT_FOUND`           |
| The key lacks `documents:write`                   | `403` `DEVELOPER.SCOPE_INSUFFICIENT` |

Repeating `_approve` on a slot that already acted is safe: it returns a state error
instead of recording a second approval.

## Next

<Columns cols={2}>
  <Card title="Document events" icon="bolt" href="/en/guides/documents/events">
    The full event list and what to do for each.
  </Card>

  <Card title="Signing" icon="pen" href="/en/guides/documents/signing">
    When your slot signs rather than approves.
  </Card>
</Columns>
