User
POST
/api/v1/user/{system_uuid}/{system_user_id}
Create a new User row for the authenticated tenant. This page is the normative internal
description for this endpoint per
ADR 0026; the
external
contract is OpenAPI
(operationId: createUser).
OpenAPI operationId (reference): createUser
Request path and behavior
Request enters the FastAPI stack in app/main.py (metrics, body size limit, API key + rate limit
per
ADR 0005, security headers, request ID), then
app/api/v1/user.py. The handler validates the body against the OpenAPI schema, enforces
idempotency for writes per
ADR 0006, and delegates to
UserService.create → UserRepository (PostgreSQL). Successful writes may record an
idempotency row for replay.
Success: 201 Created with the created user body (includes generated
client_uuid). On idempotent replay (same key + same payload hash), the stored success response
is
returned with the original status code.
Idempotency
Idempotency-Key header is required: printable ASCII, length 1–128. The handler
uses endpoint_path = "/api/v1/user" with the key and a SHA-256 hash of the canonical JSON body
for
lookup and replay. Hash mismatch → 409
IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD; missing header → 400
IDEMPOTENCY_KEY_REQUIRED. Full rules:
Unified spec — Idempotency.
Errors
Envelope shape per ADR 0003
(code,
key, message, source).
| HTTP | Code / key | When |
|---|---|---|
400 |
USER_101 / USER_CREATE_ALREADY_EXISTS |
Duplicate natural key (system_user_id, system_uuid) — logged at
warning as
create_user_duplicate in UserService.
|
400 |
COMMON_400 / IDEMPOTENCY_KEY_REQUIRED |
Write without Idempotency-Key. |
409 |
COMMON_409 / IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD |
Same idempotency key, different body hash. |
422 |
Validation | Pydantic / OpenAPI validation — global handler logs validation_error at
warning.
|
413 |
COMMON_413 |
Body over limit — middleware; request_body_limit_exceeded at warning.
|
401 / 429 |
— | API key / rate limit — before handler; see ADR 0005. |
500 |
— | Uncaught: request_failed with logger.exception. |
Logging
Baseline: every request completes with an info line from HTTP middleware —
request_done method=… path=… status=… elapsed_ms=…. request_id is propagated from
X-Request-Id per
ADR 0023
(app/core/logging.py).
User router (app.api.v1.user), create path:
create_user_idempotent_replay— info when replaying a stored success (key only).create_user_requested— info,system_user_id,system_uuid.create_user_succeeded— info after persist;client_uuidincluded.
UserService: create_user_duplicate at warning before raising;
create_user_persisted at info after insert.
Metrics
http_requests_total and http_request_duration_seconds with labels including
path_template=/api/v1/user, method=POST, and HTTP status. Database timing via
db_operation_duration_seconds where applicable. Details:
Unified spec — Metrics.
Single-file deep spec
Cross-cutting sections (layers, full error catalog context, dependencies): Unified internal spec — this operation.
Page history
| Date | Change | Author |
|---|---|---|
| Added Page history section (repository baseline). | Ivan Boyarkin |