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

# Operating a controller's project

> Register both companies, name the project's controller, take what needs approval to it by link, and keep the access review current.

Your company integrates and runs a customer's agents, and the data is theirs. This guide sets the project up the way the law expects: the controller named, its decisions taken by a link with a code in its DPO's e-mail, and your company's access reviewed every 90 days. The concepts are in [Parties and approvals](/en/concepts/approvals).

## Before you start

* A Console person token of your tenant, with the `admin` role for the entities and parties and `security` for the configuration diffs. In the examples, `NIADRA_TOKEN`, against the [control API](/en/api#authentication) at `https://control.api.niadra.com`.
* The project id (`PROJECT_ID`) and, for the diffs, the production space's id (`SPACE_ID`).
* The name, country, registry number and data protection officer's e-mail of both companies: yours and the customer's. The customer's DPO e-mail is where the links go; confirm it with them first.

## 1. Register both companies

```sh theme={null}
curl -X POST "https://control.api.niadra.com/v1/legal-entities" \
  -H "Authorization: Bearer $NIADRA_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Aurora Health Network", "country": "BR", "registry": "12345678000190", "dpo_email": "dpo@aurora.example"}'
```

The answer carries `entity_id`, the registry masked and never the e-mail. Register your own company the same way. A second call with the same registry and the same e-mail answers the entity that already exists; with another e-mail, 409, because a company's DPO does not change by a request.

## 2. Name the controller and the operator

```sh theme={null}
curl -X POST "https://control.api.niadra.com/v1/projects/$PROJECT_ID/parties" \
  -H "Authorization: Bearer $NIADRA_TOKEN" -H "Content-Type: application/json" \
  -d '{"entity_id": "'$CONTROLLER_ENTITY'", "role": "controller"}'
```

With the controller named, the project starts waiting for it. Adding your company as operator now answers 409 `controller_approval_required`: request the approval and repeat the call once it approves.

```sh theme={null}
curl -X POST "https://control.api.niadra.com/v1/approvals" \
  -H "Authorization: Bearer $NIADRA_TOKEN" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"project_id": "'$PROJECT_ID'", "kind": "operators", "subject_ref": "add:operator:'$OPERATOR_ENTITY'"}'
```

The answer is the approval, `pending`, with `approval_id`, `document_hash` and `expires_at`. The link went to the controller's DPO e-mail and is not in the answer.

## 3. What the DPO sees

The link opens the Console's public approval page. It shows what is asked, by which tenant and for which project; the DPO asks for the code, gets six digits in the same e-mail, opens the document and decides. Five codes per link, one a minute; five wrong codes close the link, and an expired or used link answers like one that never existed. The decision is made once and becomes a receipt in the project's spaces. Follow it through the API:

```sh theme={null}
curl "https://control.api.niadra.com/v1/projects/$PROJECT_ID/approvals" -H "Authorization: Bearer $NIADRA_TOKEN"
```

When the status is `approved`, repeat step 2 for the operator. An `expired` approval needs a new request.

## 4. Diffs that wait for the controller

A configuration diff that widens the space's purposes, loosens the access policy or extends a retention does not take effect when approved in your Console: it stays pending until the controller approves. Submit the diff as usual, through the Configuration screen or [`POST /v1/config/diffs`](/en/api/control/config-diffs), and request the approval with its id:

```sh theme={null}
curl -X POST "https://control.api.niadra.com/v1/approvals" \
  -H "Authorization: Bearer $NIADRA_TOKEN" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"project_id": "'$PROJECT_ID'", "kind": "purposes", "subject_ref": "'$DIFF_ID'"}'
```

The other diffs, the ones that restrict or only change operations, take effect as they always did. The control plane checks this when the diff is submitted and again when it is approved.

## 5. The impact report

Generate the report for each purpose of the space and request its approval. Since the report is stable, the controller approves a hash, and the read says when it approved this very document:

```sh theme={null}
curl "https://control.api.niadra.com/v1/projects/$PROJECT_ID/ripd?purpose=customer_service" -H "Authorization: Bearer $NIADRA_TOKEN"
# -> { "purpose": "customer_service", "sha256": "...", "markdown": "...", "document": {...}, "approved_at": null }

curl -X POST "https://control.api.niadra.com/v1/approvals" \
  -H "Authorization: Bearer $NIADRA_TOKEN" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"project_id": "'$PROJECT_ID'", "kind": "ripd", "subject_ref": "customer_service"}'
```

A configuration that changed since generates another document, with another hash and no `approved_at`: request again.

## 6. The access review

Every 90 days, the controller confirms that your company's access stays as it is. The control plane asks for the review by itself when it falls due; you follow it at:

```sh theme={null}
curl "https://control.api.niadra.com/v1/projects/$PROJECT_ID/access-review" -H "Authorization: Bearer $NIADRA_TOKEN"
# -> { "last_approved_at": "2026-07-01T12:00:00Z", "due_at": "2026-09-29T12:00:00Z", "pending": null }
```

## 7. An incident out of hours

When someone needs a role they do not have, now, without waiting for a link:

```sh theme={null}
curl -X POST "https://control.api.niadra.com/v1/projects/$PROJECT_ID/emergency-access" \
  -H "Authorization: Bearer $NIADRA_TOKEN" -H "Content-Type: application/json" \
  -d '{"space_id": "'$SPACE_ID'", "role": "security", "minutes": 120, "reason": "webhook secret leaked in a build log; rotating"}'
```

The role applies on the next token, for at most four hours. The controller is told by e-mail and can end the access by the link, and it is recorded as an approval of kind `emergency_access`, with the reason.

## 8. Passing the operation to another company

When the customer changes operator, the new operator requests the transfer, after the controller approves `transfer` with the new operator's tenant id as subject:

```sh theme={null}
curl -X POST "https://control.api.niadra.com/v1/projects/$PROJECT_ID/transfer" \
  -H "Authorization: Bearer $NEW_OPERATOR_TOKEN" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"to_tenant_id": "'$NEW_TENANT'", "approval_id": "'$APPROVAL_ID'", "grace_hours": 72, "operator_entity_id": "'$NEW_OPERATOR_ENTITY'"}'
```

The space databases stay where they are: the customers' memory continues. Your source keys keep working for 72 hours and expire at `grace_until`; the answer lists, per space, the webhook endpoints whose secrets the new operator must turn before then, with [`PUT /v1/secrets/webhook/{id}`](/en/api/secrets). An endpoint still signing with a secret of yours stops receiving when the grace period ends.

## What is recorded

Every decision, every emergency access and every transfer enters the [receipt chain](/en/concepts/receipts) of the project's spaces, which the controller can audit through its own chain. In the Console, the Project and controller screen, of the `security` role, shows the parties, the pending and decided approvals, the access review and the transfers, and the Companies screen shows the tenant's legal entities.

## Next steps

<CardGroup cols={2}>
  <Card title="Parties and approvals" href="/en/concepts/approvals">
    the model behind each step.
  </Card>

  <Card title="Console access" href="/en/concepts/console-access">
    the roles each route asks for.
  </Card>

  <Card title="Receipts and audit" href="/en/concepts/receipts">
    where each decision is recorded.
  </Card>

  <Card title="List approvals" href="/en/api/control/approvals">
    the reference of `GET /v1/projects/{project_id}/approvals`.
  </Card>
</CardGroup>
