---
title: "Create User"
method: POST
path: "/users/"
tags: ["users"]
---

# Create User

`POST /users/`

## Request

- Base URL: `https://app.zeno-works.ch` (the document declares no server; this is the origin it was published from)
- URL: `https://app.zeno-works.ch/users/`
- Auth: none declared

Create a user (admin). Hashes ``password`` if present; associates companies.

Memberships come from ``memberships`` (id + role) or the legacy ``companies``
array (roles default ``member``) — never both (422).

**``invite`` (admin surface P12) closes the hole this route shipped with.** Omitting
``password`` hashes a random string, so the row was created, the memberships attached,
a 201 returned — and the person had **no way in**. Their only route back was asking an
administrator to invent a password and send it over some channel, which is both a
support burden and the worst way to distribute a credential. With ``invite`` (the
default) a create WITHOUT a password mints a single-use code and emails it.

Reuses the password-reset OTP rather than a second single-use credential type: one
expiry, one consumption rule, one revocation path — and `create_otp` already
invalidates outstanding codes, so an invite followed by a self-service reset cannot
leave two live codes.

``invite_mail_attempted`` in the response says whether SMTP was even **configured**,
never whether the mail arrived. On a stack with no ``SMTP_HOST`` it is ``false`` and
the code exists with nobody told — an operator who reads "invited" and moves on has
stranded the person they were provisioning.

## Query parameters

- `invite` boolean

## Headers

- `authorization` string, nullable

## Cookies

- `access_token` string, nullable

## Request body

- UserCreate: ``POST /users/`` body (admin).
  - `first_name` string, required
  - `last_name` string, required
  - `email` string, email, required
  - `phone` string, nullable
  - `password` string, nullable
  - `is_admin` boolean
  - `is_active` boolean
  - `companies` string[], nullable
  - `memberships` MembershipIn[], nullable
    - `company_id` string, required
    - `role` 'owner' | 'accountant' | 'member'

## Response `201`

Successful Response

- UserResponse: ``GET/POST/PUT /users/...`` → ``{"user": ...}``.
  - `user` UserOut, required: Serialized user (no secrets). ``companies`` is an array of company ids; ``memberships`` adds the per-membership role.
    - `id` string, required
    - `first_name` string, required
    - `last_name` string, required
    - `email` string, email, required
    - `phone` string, nullable
    - `is_admin` boolean, required
    - `is_active` boolean, required
    - `ui_language` string, nullable
    - `companies` string[]
    - `memberships` MembershipOut[]
      - `company_id` string, required
      - `role` 'owner' | 'accountant' | 'member', required
  - `invite_mail_attempted` boolean, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/zeno-works/apis/zeno-api.md) · [All operations](https://skmtc.dev/zeno-works/apis/zeno-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zeno-works/apis/zeno-api/revisions/1eb93dde146c?raw)
