Skip to main content

Overview

Creating a provider involves either flagging an existing user as a provider or creating a new user with the provider flag enabled. This guide covers both scenarios.
Per-Clinic Providers: When you create a provider, they are flagged as a provider ONLY in the current clinic. The same user can be a regular staff member in other clinics.

Prerequisites

Before creating a provider:
1

Required Permissions

You must have CLINIC_ADMIN role in the clinic where you’re creating the provider.
2

Clinic Context

Ensure the correct clinic is selected in the clinic selector (top-right dropdown).
3

User Information

Prepare the provider’s email, phone number, and full name.

Method 1: Flag Existing User as Provider

Use this method when the user already exists in your clinic (e.g., administrative staff becoming a provider).
1

Navigate to Users

Go to Users section from the main menu.
2

Find the User

Use search to locate the user by name or email.
3

Open Edit Dialog

Click the Edit icon on the user’s card.
4

Enable Provider Flag

Toggle Is Provider to true.
5

Save Changes

Click Save to update the user’s profile.

API Request (Method 1)

PATCH /v1/users/{user_clinic_id}?clinic_id={clinic_id}
curl -X PATCH "https://api.clinic.example.com/v1/users/{user_clinic_id}?clinic_id={clinic_id}" \
  -H "Cookie: access_token={jwt_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "is_provider": true
  }'

Response

{
  "id": "abc-123-def-456",
  "user_id": "user-789-xyz-012",
  "full_name": "Dr. María García",
  "email": "maria.garcia@namsec.es",
  "phone_e164": "+34612345678",
  "is_provider": true,
  "clinic_role": "STAFF",
  "status": "approved",
  "created_at": "2025-12-01T10:30:00Z",
  "updated_at": "2026-01-17T09:15:00Z"
}
Existing Appointments: If the user had existing appointments scheduled BEFORE being flagged as a provider, those appointments remain valid. Future appointments will now be calculated using this user’s provider availability.

Method 2: Create New User as Provider

Use this method when hiring a new team member who will be a provider.
Formulario de creación de proveedor con campos de información y flag de proveedor
1

Navigate to Create User

Go to UsersCreate User or click the Add User button.
2

Fill User Information

  • Full Name: Provider’s full name
  • Email: Professional email address (must be unique globally)
  • Phone: E.164 format (e.g., +34612345678)
  • Role: Select clinic role (STAFF or CLINIC_ADMIN)
3

Enable Provider Flag

Toggle Is Provider to true.
4

Set Initial Status

Choose approved (active immediately) or pending (requires approval).
5

Submit Form

Click Create User to save.

API Request (Method 2)

POST /v1/users?clinic_id={clinic_id}
curl -X POST "https://api.clinic.example.com/v1/users?clinic_id={clinic_id}" \
  -H "Cookie: access_token={jwt_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "juan.lopez@namsec.es",
    "full_name": "Dr. Juan López",
    "phone_e164": "+34612345678",
    "clinic_role": "STAFF",
    "is_provider": true,
    "status": "approved",
    "password": "SecurePassword123!"
  }'

Response

{
  "id": "def-456-ghi-789",
  "user_id": "user-012-abc-345",
  "full_name": "Dr. Juan López",
  "email": "juan.lopez@namsec.es",
  "phone_e164": "+34612345678",
  "is_provider": true,
  "clinic_role": "STAFF",
  "status": "approved",
  "created_at": "2026-01-17T09:20:00Z",
  "updated_at": "2026-01-17T09:20:00Z"
}
Audit Logging: Provider creation is automatically logged in audit_logs with action USER_CREATED and details including is_provider: true.

Field Details

Email

Email must be unique globally (across all clinics). Two users cannot share the same email address.Error Example:
{
  "detail": "Email already in use"
}
Solution: Use a different email or identify the existing user and flag them as a provider instead (Method 1).
Email must be a valid email address format.Valid: maria.garcia@namsec.es, juan+provider@example.orgInvalid: maria.garcia, juan@, @namsec.es

Phone Number

Phone numbers MUST be in E.164 format: +[country code][number]Examples:
  • Spain: +34612345678
  • US: +14155551234
  • Mexico: +525512345678
Invalid: 612345678, (415) 555-1234, +34 612 345 678
Phone numbers must be unique globally (across all clinics), similar to email.Error Example:
{
  "detail": "Phone number already in use"
}
The system uses normalize_phone_to_e164() to convert various formats:
# Input: "612345678" (Spain, assuming +34 prefix)
# Output: "+34612345678"

# Input: "+34 612 345 678"
# Output: "+34612345678"
Best Practice: Always provide phone numbers in E.164 format to avoid parsing issues.

Full Name

Full name is stored in user_clinic_profiles.full_name, meaning:
  • The same user can have different names in different clinics
  • Name is tied to the user_clinic relationship, not the global users table
Example:
  • Clinic A: “Dr. María García Rodríguez”
  • Clinic B: “María García” (same user, informal name)
When a patient exists in Clinic A as “Manuel López” and books in Clinic B as “Eduardo Cassanovas”, the system:
  1. Associates the user to Clinic B (creates new user_clinic)
  2. Updates the global name to “Eduardo Cassanovas” (respects most recent input)
See docs/FIX_MULTI_CLINIC_PATIENT_NAME_UPDATE.md for full details.

Clinic Role

STAFF

Standard team member with access to their clinic’s data.

CLINIC_ADMIN

Can manage clinic configuration, users, providers, and services.

ADMIN

System administrator with access to ALL clinics (global role).
Role Independence: A user can be CLINIC_ADMIN in Clinic A and STAFF in Clinic B. Roles are per-clinic, stored in user_clinics.clinic_role_id.

Status

approved

Active Provider: Can deliver services immediately. Availability slots are generated.

pending

Awaiting Approval: Provider is not yet active. No availability slots generated. Use for onboarding.

suspended

Temporarily Inactive: Provider is suspended (leave, disciplinary action). No availability slots.

Post-Creation Steps

After creating a provider, complete their setup:
1

Configure Work Hours

Navigate to Configure Schedule to set the provider’s working hours.Without work hours: Provider will have NO availability (no slots generated).
2

Assign Services

Navigate to Assign Services to link the provider to services they deliver.Without service assignments: Provider won’t appear when patients book specific services.
3

Verify Availability

Test availability by simulating an appointment booking for a service assigned to this provider.
4

Notify Provider

Send login credentials and onboarding information to the new provider.

Common Errors

Cause: You don’t have CLINIC_ADMIN role in the selected clinic.Solution: Contact your system administrator to grant you CLINIC_ADMIN permissions for this clinic.
Cause: A user with this email already exists globally.Solution:
  1. Search for the existing user in the Users section
  2. If they exist in your clinic, use Method 1 to flag them as a provider
  3. If they exist in another clinic but not yours, add them to your clinic first (Create UserClinic association)
Cause: A user with this phone number already exists globally.Solution: Similar to email conflict. Locate the existing user and either flag them as a provider or verify the phone number is correct.
Cause: Phone number is not in E.164 format.Solution: Reformat the phone number to E.164. Examples:
  • Spain: +34 + 9-digit number
  • US: +1 + 10-digit number
  • Mexico: +52 + 10-digit number

Validation Logic

The system performs several validations when creating a provider:
# From app/api/v1/users.py (simplified)

# 1. Check email uniqueness (global)
existing_user = await db.scalar(select(User).where(User.email == email))
if existing_user:
    raise HTTPException(status_code=400, detail="Email already in use")

# 2. Check phone uniqueness (global)
existing_phone = await db.scalar(select(User).where(User.phone_e164 == phone))
if existing_phone:
    raise HTTPException(status_code=400, detail="Phone already in use")

# 3. Normalize phone to E.164
normalized_phone = normalize_phone_to_e164(phone_e164)
if not normalized_phone:
    raise HTTPException(status_code=400, detail="Invalid phone format")

# 4. Validate clinic access
if not current_user.can_manage_clinic(clinic_id):
    raise HTTPException(status_code=403, detail="Cannot manage this clinic")

# 5. Create user + user_clinic + user_clinic_profile
# ... (database operations)

Database Changes

Creating a provider modifies three tables:

1. users Table (Global Identity)

INSERT INTO users (id, email, phone_e164, password_hash, is_active)
VALUES (
  'user-012-abc-345',
  'juan.lopez@namsec.es',
  '+34612345678',
  '<bcrypt_hash>',
  true
);

2. user_clinics Table (Clinic Association + Provider Flag)

INSERT INTO user_clinics (id, user_id, clinic_id, is_provider, clinic_role_id, status)
VALUES (
  'def-456-ghi-789',
  'user-012-abc-345',  -- FK to users
  'clinic-abc-123',
  true,  -- PROVIDER FLAG
  'role-staff-uuid',
  'approved'
);

3. user_clinic_profiles Table (Per-Clinic PII)

INSERT INTO user_clinic_profiles (user_clinic_id, full_name, notes, preferences)
VALUES (
  'def-456-ghi-789',  -- FK to user_clinics (PK = FK, 1:1 relationship)
  'Dr. Juan López',
  NULL,
  '{}'::jsonb
);
GDPR Compliance: PII (full_name, notes) is stored separately in user_clinic_profiles to enable GDPR-compliant deletion without losing access audit trail.

Next Steps