sequenceDiagram
autonumber
participant C as Client
participant IDP as Mock IDP<br/>/mock-idp/*
participant A as Auth Router<br/>/auth/login
participant UAS as UserAuthService
participant GEO as GeoService
participant DB as PostgreSQL<br/>(app schema)
note over C,IDP: Step 1 — Obtain IDP token
C ->> IDP: POST /mock-idp/authenticate<br/>{ idp_user_id }
IDP -->> C: { access_token: JWT(sub=idp_user_id) }
note over C,DB: Step 2 — Consent-Aware Token Exchange
C ->> A: POST /auth/login<br/>{ idp_token, has_consented, invitation_id, ... }
A ->> IDP: GET /.well-known/jwks.json
IDP -->> A: { keys: [...] }
A ->> A: jwt.decode → extract sub claim
A ->> UAS: authenticate(context)
UAS ->> DB: identify_idp_user(idp, login_id)
alt Returning user
DB -->> UAS: User found
opt Returning user with consent
UAS ->> DB: Register consent (if has_consented)
end
opt Returning user with invitation
UAS ->> DB: process_invitation(user, invitation_id)
end
else New user
DB -->> UAS: No user → register_new_user()
UAS ->> DB: INSERT User (age_class, language, country)
UAS ->> GEO: resolve country from IP
GEO -->> UAS: country code (FR/US/...)
UAS ->> DB: INSERT IDPLogin
opt Has consented
UAS ->> DB: INSERT UserConsent
end
opt Has invitation
UAS ->> DB: process_invitation(user, invitation_id)
UAS ->> DB: INSERT ParentalConsent (implicit)
UAS ->> DB: INSERT CollectiveMembership
end
end
opt Parent user type
UAS ->> DB: Ensure parent has a collective<br/>(auto-create "Family")
end
UAS ->> UAS: issue_app_session(consent check)
alt enforce_consent_policy AND consent incomplete
UAS -->> A: bearer_token = None
else Consent OK or policy disabled
UAS ->> DB: UPSERT AppSession (scope=ONBOARDING|PENDING_CONSENT|FULL)
DB -->> UAS: AppSession
end
UAS ->> DB: COMMIT
opt Returning user (full consent)
UAS ->> DB: get_user_history()
DB -->> UAS: UserHistory
end
UAS -->> A: AuthResponse { bearer_token, profile, user_history }
A -->> C: { bearer_token, profile (user + consent_verification + app_session) }
Client Development & Integration
Last updated: 2026-05-26
Client Development Guide
This guide specifies the protocol for building client applications (Web, Mobile, Bots) that interact with the StopCyberViolences API. It covers consent-aware authentication, session scoping, capability negotiation, feature flags, interactive prompts, real-time streaming, collective management, and the parental authorization flow.
1. Client Identification & Origin Tracking
All clients must identify themselves using an Origin Identifier. This field is mandatory for telemetry tracking (Langfuse) and capability profile resolution.
- Field:
origin(string) - Mandatory - Common Values:
web_react,discord,dev_terminal. - Custom Clients: Use a unique slug (e.g.,
ios_app,android_app).
2. Authentication Flow
The application uses a consent-aware, IDP-based authentication flow. Clients obtain a signed token from an Identity Provider (IDP) and exchange it for an application-scoped session. Consent status, invitations, and Geo-IP are all evaluated during a single atomic authenticate() call.
Step 1 — Obtain an IDP Token
Clients authenticate with the Identity Provider (e.g., Orange IDP, Google, or the mock IDP for development) and receive a JWT token.
Development (Mock IDP):
POST /api/v1/mock-idp/authenticate
Body: { "idp_user_id": "test-user-123" }
# Returns: { "access_token": "<signed-jwt>" }The mock IDP signs the token with a symmetric key (HS256). Its public key is exposed at GET /mock-idp/.well-known/jwks.json so the auth router can verify it without out-of-band configuration.
Step 2 — Consent-Aware Token Exchange
POST /api/v1/auth/login
Request Body (AuthIDPRequest):
{
"idp_token": "<jwt-from-step-1>",
"id_provider": "mock-idp",
"user_type": "teenager",
"age_class": "teenager 15-17",
"language": "FR",
"origin": "web_react",
"capabilities": {
"supports_sse": true,
"supports_geoip": true
},
"has_consented": false,
"invitation_id": null
}| Field | Type | Required | Description |
|---|---|---|---|
idp_token |
string | Yes | JWT obtained from the IDP |
id_provider |
string | Yes | "mock-idp" (or future real IDPs) |
user_type |
string | Yes | "teenager" or "parent" |
age_class |
string | No | "child <11", "preteen 11-14", "teenager 15-17", "adult" |
language |
string | Yes | 2-letter language code ("FR", "EN", "ES", "IT", "DE", "PT") |
origin |
string | Yes | Client identifier — "web_react", "discord", "dev_terminal", or custom |
capabilities |
object | No | Client capabilities (see Capability Negotiation) |
has_consented |
boolean | No | Whether user accepted TOS during onboarding (default false) |
invitation_id |
UUID | No | Invitation token for joining a collective (from an invite URL/QR code) |
Response (AuthResponse):
{
"bearer_token": "550e8400-e29b-41d4-a716-446655440000",
"profile": {
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"user_type": "teenager",
"language": "FR",
"age_class": "teenager 15-17",
"country": "FR"
},
"app_session": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"scope": "pending_consent",
"origin": "web_react"
},
"consent_verification": {
"has_consented": false,
"needs_parental_consent": true,
"has_parental_consent": false
}
},
"user_history": null
}Key fields:
| Field | Description |
|---|---|
bearer_token |
UUID to use as Authorization: Bearer <token>. Can be null if enforce_consent_policy is true and consent is incomplete — in this case the client cannot make authenticated API calls. |
profile.user |
The resolved user record — contains id, user_type, language, country (from Geo-IP), age_class |
profile.app_session.scope |
Session scope: "onboarding" → "pending_consent" → "full". Drives what UI to show (see Session Scoping). |
profile.consent_verification |
Consent status: has_consented (TOS accepted?), needs_parental_consent (is a minor?), has_parental_consent (parent approved?) |
user_history |
Previous conversations for session resumption. Only returned for returning users with full consent. null for new users or users with incomplete consent. |
⚠️
bearer_token = null: When consent enforcement is active and the user hasn’t completed the consent pipeline, no token is issued. The client should display onboarding/consent UI and guide the user throughPATCH /auth/profileto accept TOS, orPOST /auth/request-authorizationto request parental consent. Re-authenticate after consent is granted.
Consent-Based Session Scoping
The profile.app_session.scope field tells the client exactly what UI to show:
| Scope | Condition | Client behavior |
|---|---|---|
"onboarding" |
No TOS accepted yet | Show settings/profile form. Call PATCH /auth/profile with has_consented: true to advance. |
"pending_consent" |
TOS accepted, minor lacks parental consent | Block chat access. Show consent gate with option to request authorization. |
"full" |
All consent requirements satisfied | Unrestricted chat access. Proceed to /chat/init. |
After updating consent via PATCH /auth/profile, re-authenticate to get a refreshed scope. Consent enforcement is controlled by two feature flags — see Feature Flags.
3. Capability Negotiation
The system uses a 3-tier negotiation system to adapt its behavior to the client’s features.
Priority Levels
- Explicit Declaration: Capabilities provided by the client in the
auth/loginrequest (Highest priority). - Registry Lookup: Backend looks up the
originin theclient_registry(defined inapi/config.py). - Safe Defaults: System-wide safe defaults used if the origin is unknown.
Note: If the provided capabilities fail validation, the system automatically falls back to the origin’s registry profile or safe system defaults.
Supported Capabilities
| Flag | Type | Description |
|---|---|---|
supports_sse |
Boolean | If true, the backend enables streaming callbacks (Server-Sent Events) for this session. |
supports_geoip |
Boolean | If true, the backend resolves the client’s IP to a country code during user registration. The detected country is stored on User.country and pre-fills the settings panel. |
4. Feature Flags
Before rendering the UI, clients should fetch the current feature flags:
GET /api/v1/features?variant=teenager&environment=development
Response:
{
"enable_monster_flip_animation": true,
"enable_simple_resume_prompt": true,
"enable_login_screen": true,
"collective_management": false,
"enable_simple_qr_invitation": false,
"enable_settings_panel": true,
"enforce_consent_gate": false
}Flags are evaluated against variant and environment context and should be re-fetched on each session start.
| Flag | Visibility | Description |
|---|---|---|
enable_monster_flip_animation |
all | 3D coin-flip transition on monster avatar change |
enable_simple_resume_prompt |
all | Floating resume card instead of full history modal |
enable_login_screen |
all | Manual login screen before auto-login |
enable_settings_panel |
all | Full SettingsPanel replaces simple language selector |
enforce_consent_gate |
all | ConsentGate blocks chat until parental consent is granted |
collective_management |
parent | Collective admin dashboard and invitations |
enable_simple_qr_invitation |
parent | Simplified QR code modal for invitations |
5. Session Lifecycle
Initializing a Chat Session
Endpoint: POST /api/v1/chat/init
After authentication, clients must initialize a chat session before sending messages. This endpoint requires the Authorization: Bearer <bearer_token> header obtained from /auth/login.
Request Body:
{
"resume_conversation_id": null
}Response Metadata: The response contains a session_id (a UUID) which must be included in all subsequent requests.
Resuming a Previous Conversation
If user_history.conversations is non-empty, offer the user the option to resume. To resume, call /chat/init with the specific resume_conversation_id. If enable_simple_resume_prompt is true, the backend will automatically restore the conversation summary from the persistent store and prime the agent with the previous context.
Sending Messages
Endpoint: POST /api/v1/chat
{
"message": "Hello, I need help.",
"session_id": "uuid-from-init",
"attachments": ["path/to/previously/uploaded/file.png"]
}Interactive State Selection
When the agent requires structured input (emotion selection, multiple-choice question), the ChatResponse includes an interactive_prompt field:
{
"session_id": "uuid-from-init",
"messages": [ ... ],
"session_state": { ... },
"interactive_prompt": {
"prompt_style": "choice",
"question": "On which platform did this happen?",
"options": [
{ "value": "instagram", "label": "Instagram", "emoji": "📸" },
{ "value": "tiktok", "label": "TikTok", "emoji": "🎵" },
{ "value": "other", "label": "Other", "emoji": "❓" }
],
"allow_free_text": false
}
}| Field | Type | Description |
|---|---|---|
interactive_prompt |
object or null |
Present when the agent awaits user input |
prompt_style |
"choice" or "emotion" |
UI rendering mode |
question |
string | The question text to display |
options |
array | List of { value, label, emoji? } choices |
allow_free_text |
boolean | If true, offer a free-text “Other” input in addition to the options |
Sending the selection: The user’s choice should be sent through the normal POST /chat endpoint as a standard message. The backend resolves the selection against current agent state — there are no separate /chat/emotion or /chat/dropdown endpoints.
{
"message": "instagram",
"session_id": "uuid-from-init"
}When the next ChatResponse arrives, interactive_prompt will be null (selection accepted) or contain a new prompt (follow-up question).
Handling Dynamic State
Clients must synchronize their UI based on the ChatResponse metadata returned in every turn:
- Active Monster: Check the latest message’s monster field in
messages[]. - Interactive Prompt: When
interactive_promptis notnull, render the appropriate choice or emotion selector. When it isnull, show the normal chat input. - Language/Settings: Changes are broadcast via SSE
settingsevents.
6. Real-time Streaming (SSE)
If supports_sse is negotiated as true, establish a persistent connection for real-time updates.
Endpoint: GET /api/v1/chat/{session_id}/events/listen
Event Protocol
- handshake: Payload:
{}(connection confirmation) - settings: Payload:
{"data": {"monster": "..."}}or{"data": {"language": "..."}}(state updates) - terminate: Payload:
{}(stream end signal)
7. Conversation History
Endpoint: GET /api/v1/chat/history
Returns a UserHistory object with all past conversations for the authenticated user. Used to populate the ConversationHistoryModal or the SimpleResumePrompt in the web client.
8. Session Settings
Endpoint: POST /api/v1/chat/settings/change
Allows changing the language or the monster for the active session.
Request Body:
{
"session_id": "uuid",
"language": "EN",
"monster": "moustache"
}9. Media & Accessibility
File Uploads
Upload media (screenshots, pasted images) to POST /api/v1/files/upload before sending the chat message. Use the returned file paths in the attachments array. Images pasted directly into the chat input are automatically uploaded by the web client.
Voice Integration
Use POST /api/v1/audio/transcribe to convert speech to text.