# Client access

**A client collaborator is someone outside your agency, invited to one named project.** They join your Workspace with the `client` role and see that project — its cycles, its tasks, its messages, and the work you publish to them. Nothing else in the Workspace exists for them.

Access is granted **one project at a time**. There is no "share everything" switch, and no group membership that widens it: a client sees exactly the projects they were individually invited to.

## Prerequisites

- The `owner` or `admin` role. A `member` cannot invite, revoke, or change a collaborator.
- A project to share, and your customer's email address — the invitation is bound to that exact address.
- Nothing else. hlix emails the invitation, and the invite response also carries the link in case you want to pass it on yourself.

## What a client collaborator can do

| Grant | What it allows |
| --- | --- |
| Default | See the shared project, read its cycles, tasks and messages, and comment. |
| `canSteer` | Additionally start and drive an agent run on that project. |
| — | Project administration is refused for every client, with or without `canSteer`. |

Secrets, environments, integrations, model configuration, logs, events, the approval queue and the audit log are invisible to the `client` role, not merely refused. See [Roles & permissions](/security/roles-and-permissions/) for the full matrix and [Tenancy & isolation](/security/tenancy/) for the boundary underneath it.

## Invite a client

**This is the supported surface.** Open the project, go to **Collaborators**, enter the customer's email and choose whether they may steer. The invitation is emailed immediately and appears in the pending list, where you can revoke it.

The same panel lists current collaborators, toggles `canSteer` per person, and removes access.
  **Collaborators and invitations are not in the published API contract**, so no generated SDK method covers them. The dashboard is the primary surface; the call below is available for scripting.

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/projects/$PROJECT_ID/invitations" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID" \
  -H 'content-type: application/json' \
  -d '{"email": "sam@acme.example", "canSteer": false}'
```

Expected output (`201`):

```json
{
  "id": "…",
  "projectId": "…",
  "email": "sam@acme.example",
  "canSteer": false,
  "status": "pending",
  "expiresAt": "2026-09-04T09:14:02.118Z",
  "token": "…",
  "acceptUrl": "https://app.hlix.ai/projects/…/invitations/…/accept",
  "emailSent": true
}
```

`emailSent: false` means the invitation was created but the email did not go out. The invitation is still valid — send `acceptUrl` to your customer yourself.
  ## What happens when they accept

Accepting the link does three things at once: it marks the invitation used, creates the person's membership of your Workspace with the `client` role, and grants them the project with the `canSteer` value the invitation carried.

Before you send one:

- **The link is bound to the invited email.** Signing in as a different account and opening it is refused, not silently accepted under the other identity.
- **It expires after seven days.**
- **Re-inviting the same address rotates the link.** The previous one dies immediately, so the newest email is always the one that works.
- **Accepting twice is a no-op, not a re-grant.** If you removed the person or turned off `canSteer`, clicking the still-unexpired link again does not restore anything.
- **Removing a collaborator kills their pending links** for that project as well as their access.

## The customer record is not an access path

A **client account** is the company you deliver for — the customer an invoice is addressed to and a project can be assigned to. Recording that a person works for that company grants them **nothing**: it does not open a single project.

Project access comes from project invitations and from nothing else. Deriving access from company membership would widen it to every project for that customer, including ones nobody shared.

**A project a client was not invited to answers 404:** Not `403`. A "forbidden" would confirm the project exists, which is itself a disclosure. Never read a `404` as proof that something is absent — see [Tenancy & isolation](/security/tenancy/#why-a-strangers-project-answers-404).

## Remove or downgrade access

Turning off `canSteer` leaves the person able to read and comment. Removing them ends their access to that project entirely; if it was their only project, they keep a Workspace membership that reaches nothing.

Every invite, revoke, permission change, removal and acceptance is written to the [audit log](/security/audit-log/).

## If an invitation fails

- **`403` with `{"error": "admin only"}`** — a `member` tried to invite, list, revoke or remove. Only `owner` and `admin` may.
- **`404` with `{"error": "Project not found"}`** — the project is not in the Workspace the request named. Check `X-Organization-Id`.
- **`409` with `{"error": "Another invitation for this email is being sent right now — try again in a moment."}`** — two invites for the same address were sent at the same moment. Retry.
- **`403` with `{"error": "This invitation was issued to a different email"}`** — your customer is signed in as another account. They must accept as the invited address.
- **`409` with `{"error": "Invitation is no longer valid"}`** — revoked, or superseded by a newer invite for the same address. Send a fresh one.
- **`400` with `{"error": "Invalid or expired invitation"}`** — the link is unknown or past its seven days.
- **`404` with `{"error": "Collaborator not found"}`** — already removed, or never granted on this project.
- **`403` with `{"error": "Read-only access to this project"}`** — the client tried to start a run without `canSteer`. Grant it per project if you want them to.
- **`403` with `{"error": "Project administration is restricted to the agency"}`** — a client attempted a configuration change. `canSteer` does not lift this; nothing does.
- **They accepted but see nothing.** They are signed into the wrong Workspace. Accepting returns the Workspace to switch to; the client surfaces live under that Workspace, not their own.

## Where to go next

| If you want to… | Read |
| --- | --- |
| See what your customer sees | [The client portal](/clients/portal/) |
| Publish work and ask for a decision | [Deliverables & approvals](/clients/deliverables/) |
| Read the full role matrix | [Roles & permissions](/security/roles-and-permissions/) |
| Bill this customer | [Invoicing your client](/clients/invoicing/) |

## Next steps

[The client portal](/clients/portal/)
  [Deliverables & approvals](/clients/deliverables/)
  [Roles & permissions](/security/roles-and-permissions/)
  [Tenancy & isolation](/security/tenancy/)