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.

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) }

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.

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

  1. Explicit Declaration: Capabilities provided by the client in the auth/login request (Highest priority).
  2. Registry Lookup: Backend looks up the origin in the client_registry (defined in api/config.py).
  3. 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_prompt is not null, render the appropriate choice or emotion selector. When it is null, show the normal chat input.
  • Language/Settings: Changes are broadcast via SSE settings events.

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

  1. handshake: Payload: {} (connection confirmation)
  2. settings: Payload: {"data": {"monster": "..."}} or {"data": {"language": "..."}} (state updates)
  3. 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.

10. General Implementation Checklist