Authentication
Session cookie issuance, credential management, email verification, and GDPR self-service endpoints under /api/auth.
Authentication
The Authentication API issues and revokes session cookies, manages credentials, verifies email ownership, and exposes the GDPR self-service endpoints. All routes are mounted under /api/auth and respond with JSON unless noted.
Session cookie
Successful signup, login, and password/change calls set an outsend_session cookie:
| Attribute | Value |
|---|---|
| Name | outsend_session |
| TTL | 7 days (SESSION_DURATION_DAYS = 7) |
HttpOnly |
true |
Secure |
true (production) |
SameSite |
Lax |
Path |
/ |
The cookie is a signed token bound to a row in sessions. Revoking a session (logout, password change, account delete) deletes the row server-side even if the cookie is replayed.
Rate limits and errors
Each endpoint applies per-IP and per-identity windows (see Limits). Exhaustion returns 429 Too Many Requests with a French message containing the retry-after delay in seconds.
All errors follow FastAPI's { "detail": "<message>" } shape. Generic codes: 400 (invalid payload, expired token, wrong current password, captcha failure), 401 (bad credentials or missing session on protected routes), 429 (rate limit). Endpoint-specific detail messages are listed inline below.
POST /api/auth/signup
Creates a user, sends the welcome + verification email, and opens a session. No auth. Rate limit: 3 / hour / IP.
Request body
| Field | Type | Notes |
|---|---|---|
email |
string (email) | Required. |
password |
string | 8 to 128 chars, must contain a letter AND a digit/symbol. |
invitation_code |
string | 1 to 64 chars. Alpha is invite-only. |
accept_responsibility |
boolean | Must be true. |
hcaptcha_token |
string or null | Required when HCAPTCHA_SECRET is configured. |
{
"email": "ada@example.com",
"password": "lovelace-1843",
"invitation_code": "ALPHA-7K2",
"accept_responsibility": true,
"hcaptcha_token": "10000000-aaaa-bbbb-cccc-000000000001"
}
Response — 200 OK
{
"ok": true,
"user": {
"id": 42,
"email": "ada@example.com",
"is_admin": false,
"is_active": true,
"email_verified": false,
"created_at": "2026-05-27T09:14:00Z"
}
}
Sets the outsend_session cookie.
Specific errors
| Status | Detail |
|---|---|
400 |
Captcha invalide. Réessaie. |
400 |
Code invitation invalide |
400 |
Email existe déjà |
POST /api/auth/login
Validates credentials and opens a session. No auth. Rate limit: 5 / 15 min / IP and 5 / 15 min / email.
Request body
| Field | Type | Notes |
|---|---|---|
email |
string (email) | Required. |
password |
string | 1 to 128 chars. |
{ "email": "ada@example.com", "password": "lovelace-1843" }
Response — 200 OK
{ "ok": true, "user": { "id": 42, "email": "ada@example.com", "is_admin": false, "is_active": true, "email_verified": true, "created_at": "2026-05-27T09:14:00Z" } }
Sets the outsend_session cookie.
Specific errors
| Status | Detail |
|---|---|
401 |
Email ou mot de passe incorrect |
401 |
Compte désactivé |
POST /api/auth/logout
Revokes the current session and clears the cookie. Auth optional. Empty body. Response: 200 OK { "ok": true }.
GET /api/auth/me
Returns the currently authenticated user.
{
"id": 42,
"email": "ada@example.com",
"is_admin": false,
"is_active": true,
"email_verified": true,
"created_at": "2026-05-27T09:14:00Z"
}
POST /api/auth/password/reset-request
Sends a reset link to the email if (and only if) it matches an active user. The response is identical in every case to prevent account enumeration. No auth. Rate limit: 3 / hour / IP and 3 / hour / email (silent when exhausted).
Request body
{ "email": "ada@example.com" }
Response — 200 OK
{ "ok": true }
POST /api/auth/password/reset-confirm
Consumes a single-use reset token and sets the new password. Revokes every existing session for the user. No auth (the token is the credential).
Request body
| Field | Type | Notes |
|---|---|---|
token |
string | 10 to 256 chars, delivered by email. |
new_password |
string | 8 to 128 chars, letter + digit/symbol. |
{ "token": "eyJ...", "new_password": "babbage-1822" }
Response — 200 OK
{ "ok": true }
Specific errors
| Status | Detail |
|---|---|
400 |
Lien invalide ou expiré |
422 |
Password complexity rejected by validator. |
POST /api/auth/password/change
Rotates the password for a logged-in user. Requires the current password, revokes other sessions, issues a fresh cookie. Rate limit: 5 / hour / user.
Request body
| Field | Type | Notes |
|---|---|---|
current_password |
string | 1 to 200 chars. |
new_password |
string | 8 to 200 chars, must differ from current. |
{ "current_password": "lovelace-1843", "new_password": "babbage-1822" }
Response — 200 OK
{ "ok": true }
Sets a refreshed outsend_session cookie.
Specific errors
| Status | Detail |
|---|---|
400 |
Mot de passe actuel incorrect |
400 |
Le nouveau mot de passe doit être différent de l'actuel |
POST /api/auth/email/verify
Consumes a single-use verification token and flips email_verified to true. No auth.
Request body
{ "token": "eyJ..." }
Response — 200 OK
{ "ok": true }
Specific error: 400 Lien de vérification invalide ou expiré.
POST /api/auth/email/resend-verify
Re-sends the verification email to the authenticated user. Idempotent when the address is already verified. Empty body. Rate limit: 3 / hour / user.
Response — 200 OK
{ "ok": true }
or, if already verified:
{ "ok": true, "already_verified": true }
DELETE /api/auth/me
Permanently deletes the account and every owned record (jobs, pipelines, surveillances, sessions, tokens). Job files on disk are purged after the cascading DB delete. Feedback threads are anonymised rather than removed.
Request body
| Field | Type | Notes |
|---|---|---|
confirm_email |
string | Must equal the user's email (case-insensitive). |
{ "confirm_email": "ada@example.com" }
Response — 204 No Content
Empty body. Clears the outsend_session cookie.
Specific error: 400 Confirmation email incorrecte.
GET /api/auth/me/export
GDPR portability endpoint. Streams a ZIP archive containing every record owned by the user.
Response — 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="outsend-export-<local>-<YYYY-MM-DD>.zip"
Archive layout:
| Entry | Contents |
|---|---|
account.json |
Account metadata, no secrets. |
jobs.json |
All jobs with metadata. |
jobs/<job_id>/* |
CSV/JSON outputs for every done job. |
pipelines.json |
Pipeline definitions. |
veille.json |
recurring_scraps + run history. |
manifest.txt |
Human-readable summary. |