Skip to main content

Create new user (Admin only)

POST 

/company/users/

Create a new user for the company. Only administrators can create users.

Objective

Allow administrators to add new users to their company with control over the user limit based on their subscription plan.

Use Cases

  • Add a new employee to the platform
  • Create an account for a new manager
  • Add administrative users

Authentication & Authorization

  • Requires a valid JWT (middleware m.isLogged)
  • Requires admin or dev role (middleware m.isAdmin)
  • Requires UTC validation (mTools.checkUTC)
  • Verifies the user limit of the plan (mPlan.canCreateUser)

Behavior

  • Validates that the email does not exist in the database
  • If no password is provided, one is automatically generated with tools.generatePass()
  • Creates a user with model.createData()
  • Adds the user to the company.users[] array
  • Records usage in the billing service (BillingService.recordUsage)
  • Sends an email with credentials using mail.sendNewPass()

Validations

  • Email required and unique
  • User is added to the admin's company
  • Billing service verifies the user limit of the plan
  • If adding to the company fails, the user is deleted (rollback)

Password Handling

  • If a password is provided in the body, that one is used
  • If not provided, one is automatically generated: 8 characters, uppercase, lowercase, numbers
  • The password is hashed with model.getPassword()
  • It is sent to the user via email

Validation Flow

flowchart TD
A[Receive POST /] --> B\{Admin User?\}
B -->|No| C[403 Forbidden]
B -->|Yes| D\{Email Provided?\}
D -->|No| E[400 FORM_DATA_NOT_VALID]
D -->|Yes| F\{Email Exists?\}
F -->|Yes| G[406 USER_ALREADY_EXIST]
F -->|No| H\{Plan Allows User?\}
H -->|No| I[403 PLAN_LIMIT_REACHED]
H -->|Yes| J[Generate Password if none]
J --> K[Create User model.createData]
K --> L[Add to company.users]
L --> M\{Save Company?\}
M -->|No| N[Delete user and 400]
M -->|Yes|

<Heading
id={"request"}
as={"h2"}
className={"openapi-tabs__heading"}
children={"Request"}
>
</Heading>

<ParamsDetails
parameters={undefined}
>

</ParamsDetails>

<RequestSchema
title={"Body"}
body={{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"New user email (unique in the database)","example":"nuevo_usuario@empresa.com"},"name":{"type":"string","minLength":2,"maxLength":50,"description":"User name","example":"Juan"},"lastname":{"type":"string","minLength":2,"maxLength":100,"description":"User's Last Names","example":"Pérez García"},"password":{"type":"string","minLength":8,"maxLength":50,"description":"User password (optional). If not provided,\none is automatically generated. Requirements: minimum 8 characters,\nat least 1 uppercase letter, 1 lowercase letter, 1 number.","example":"Contraseña123","nullable":true},"role":{"type":"string","enum":["dev","admin","gestor"],"description":"User role (default: manager). Defines permissions on the platform.\n- dev: Superadmin with full access\n- admin: Full company management\n- manager: Daily operations","example":"gestor","default":"gestor"},"i18n":{"type":"string","enum":["es","en","fr","de"],"description":"User's preferred language","example":"es","default":"es"}}},"example":{"email":"nuevo_usuario@empresa.com","name":"Juan","lastname":"Pérez García","password":"MiContraseña123","role":"gestor","i18n":"es"}}}}}
>

</RequestSchema>

<StatusCodes
id={undefined}
label={undefined}
responses={{"200":{"description":"User created successfully","content":{"application/json":{"schema":{"type":"object","description":"Represents a company user (company_user) with access to the Cargoffer platform.\n**Functionality**: - Employee user of a carrier company who manages auctions, deliveries, and documentation - Authentication via JWT (tokens with configurable expiration based on refresh_time) - Hierarchical role system: dev > admin > manager (descending permissions) - Associated with a company (company.users array contains references to user _ids)\n**Model**: `src/features/models/company_user.model.js`\n**Controllers**: - `src/features/company/users/company_user.account.controller.js` (CRUD, passwords) - `src/features/company/users/company_user.profile.controller.js` (profile, preferences) - `src/features/company/auth/auth.account.controller.js` (login, tokens)\n**Middleware**: `src/features/common/middleware/company.middleware.js` - `m.isLoged` - Verifies valid JWT - `m.isGestor` - Requires 'gestor' role or higher - `m.isAdmin` - Requires 'admin' or 'dev' role","properties":{"_id":{"type":"string","description":"MongoDB unique identifier for the user (24 hexadecimal characters). Automatically generated upon registration. Referenced in: company.users[], auction.user, delivery.user, notifications.user. Included in the JWT payload as the '_id' claim.","pattern":"^[a-f0-9]{24}$","example":"63d7907cbe76403b35da63df"},"email":{"type":"string","format":"email","description":"User's unique email (used for login). **Required** - Unique in the database (unique index in production). Automatically normalized to lowercase (pre-save hook). Used for: authentication, password recovery, notifications. Validated with standard email format.","example":"usuario@cargoffer.com"},"name":{"type":"string","description":"User name (firstName in international context). Used in: interface, CMR document signature, identification in notifications. Minimum 2 characters (frontend validation).","minLength":2,"maxLength":50,"example":"Juan"},"lastname":{"type":"string","description":"User's last name (lastName in API, lastname in model). Used together with 'name' for full identification in official documents. Concatenated in: contracts, CMRs, invoices.","minLength":2,"maxLength":100,"example":"García López"},"role":{"type":"string","enum":["dev","admin","gestor"],"description":"User role that defines permissions on the platform. **dev**: Full access + debugging + management of all companies (superadmin) **admin**: Full management of their company (create users, view billing, modify data) **manager**: Daily operations (create auctions/deliveries, view dashboard, edit their profile)\n**Middleware checks**: - m.isManager accepts: ['manager', 'admin', 'dev'] - m.isAdmin accepts: ['admin', 'dev']\nStored in JWT payload (used for permission validation).","default":"gestor","example":"admin"},"status":{"type":"boolean","description":"User active/inactive status. **true**: User can log in and operate normally **false**: User is blocked (cannot log in, JWTs are invalid)\nUsed in: m.isLoged middleware checks status=true before allowing access. Changed via: POST /company/users/status/:id (admin only) Different from 'deleted' (soft delete) - this is a reversible block.","default":true,"example":true},"reason":{"type":"string","enum":["NONE","BAD_USER","PENDING","ACTIVE","BLOCKED"],"description":"User status reason code. **NONE**: Normal active user without issues **PENDING**: Registration completed, awaiting email activation **ACTIVE**: Verified and operational user **BAD_USER**: Blocked for misconduct (reports, fraud) **BLOCKED**: Administratively blocked (non-payment, security)\nUsed alongside 'status' and 'reasonMessage' for auditing. Stored with 'reasonDate' (timestamp of the change).","default":"NONE","example":"ACTIVE"},"reasonMessage":{"type":"string","description":"Descriptive message of the reason for the block/current status. Optional - Filled out by admin when changing status to false. Visible to: admins in the user panel, blocked user in the login error message. Example: Account suspended for non-payment, User reported for improper practices","example":"Usuario bloqueado temporalmente por verificación de documentación"},"reasonDate":{"type":"string","format":"date-time","description":"Timestamp of when the 'reason' field last changed. Automatic - Set in pre-save hook upon detecting a change in 'reason'. Used in: auditing, calculating lockout duration, change history.","example":"2025-01-15T14:30:00.000Z"},"i18n":{"type":"string","enum":["es","en","fr","de"],"description":"User's preferred language code (internationalization). **es**: Spanish (default for Spain) **en**: English (international default)\nUsed in: automated emails, user interface, error messages, CMR documents. Stored in JWT payload for backend personalization. Changed via: POST /company/users/lang","default":"es","example":"en"},"birthDate":{"type":"string","format":"date-time","description":"User's date of birth (optional). Used for: minimum age validations (18 years for drivers), demographic statistics. ISO 8601 format. Nullable.","nullable":true,"example":"1990-05-15T00:00:00.000Z"},"emailVerified":{"type":"boolean","description":"Indicates whether the user has verified their email. **false**: User registered but has not clicked the activation link. **true**: Email confirmed via activation token.\nFlow: register → email with token → GET /company/auth/activate/:token → emailVerified=true. Users with emailVerified=false may have limited functionality (depends on configuration).","default":false,"example":true},"emailVerifiedDate":{"type":"string","format":"date-time","description":"Timestamp of when the email was verified (null if emailVerified=false). Automatically set upon confirming the activation token. Used for: auditing, calculating time since verification, re-verification requirements.","nullable":true,"example":"2025-01-10T09:20:30.000Z"},"phone":{"type":"string","description":"User's phone number (optional but recommended). Format: international preferred (+country code), minimum 9 digits, maximum 15. Used for: logistics coordination, SMS alerts, emergency contact. Validation: `minlength: 9, maxlength: 15` in model.","pattern":"^\\+?[0-9]{9,15}$","example":"+34612345678"},"lastSignInAt":{"type":"string","format":"date-time","description":"Timestamp of the user's last successful login. Automatically updated in the login controller (POST /company/auth/login). Used in: activity dashboard, inactive account detection, security audit.","example":"2025-10-23T08:15:45.000Z"},"lastSignInIp":{"type":"string","description":"IP address from which the last login was made. Extracted from req.ip or req.headers['x-forwarded-for']. Used in: detection of suspicious access, security logs, geolocation of access.","format":"ipv4","example":"192.168.1.100"},"country":{"type":"string","description":"ISO 3166-1 alpha-3 country code of the user (3 lowercase letters). Used in: timezone filters, date formats, locale settings, TaxID validations. Default: esp (Spain). Must exist in the 'countries' collection.","pattern":"^[a-z]{3}$","default":"esp","example":"esp"},"timezone":{"type":"string","description":"User's time zone in IANA Time Zone Database format. Used in: UTC timestamp conversion to local, date display, calculation of upload/download schedules. Must match valid zones from the moment-timezone library. Default: Europe/Madrid","default":"Europe/Madrid","example":"Europe/Madrid"},"taxid":{"type":"string","description":"Personal tax identification number (NIF, DNI, NIE, etc.). **Unique in production** (unique index). Minimum 6 characters, maximum 9. Automatically normalized to uppercase (uppercase: true in model). Used in: billing, contracts, legal documentation. Temporary default: '---------' (9 hyphens) to allow creation without data.","minLength":6,"maxLength":9,"example":"12345678A"},"img":{"type":"string","format":"uri","description":"URL of the user's profile image (stored in AWS S3). Optional - Uploaded via the documents endpoint with multerS3. Used in: interface avatar, visual signature in documents. Format: Full S3 bucket URL.","nullable":true,"example":"https://cargoffer-storage.s3.amazonaws.com/users/63d7907cbe76403b35da63df.jpg"},"recovery_token":{"type":"string","description":"Temporary password recovery token (hash). Generated at: POST /company/auth/recovery (reset request) Used at: GET /company/auth/recovery/:token (validate token) Cleared after: successful password change or expiration (48h) Default: '' (empty string when no active recovery process)","example":"a7f5e8d3c2b9..."},"refresh_time":{"type":"integer","enum":[1,3,5,10],"description":"JWT expiration time in days. Used in: token generation (exp claim = iat + refresh_time * 24h). **1**: Maximum security (daily re-login) - For sensitive roles **3**: Default security/convenience balance **5**: Frequent use **10**: Mobile apps with offline capability\nStored in JWT payload, validated in m.isLoged middleware.","default":3,"example":3},"is_bot":{"type":"boolean","description":"Indicates whether the user is an automated bot (for API integrations). **true**: User created for automated integration (webhooks, scripts) **false**: Real human user\nUsed in: statistics, auditing, excluding bots from human activity metrics. Bots may have special permissions and different rate limits.","default":false,"example":false},"deleted":{"type":"boolean","description":"Soft delete flag (mongoose-delete plugin). **false**: Active user **true**: Deleted user (does not appear in normal queries)\nQueries automatically include deleted:false (plugin overrideMethods:true). Recoverable via: POST /company/users/disabled/reactivate/:id Related to the 'deletedAt' timestamp.","default":false,"example":false},"deletedAt":{"type":"string","format":"date-time","description":"Timestamp of when the user was deleted (soft delete). Null if deleted=false. Automatically set by the mongoose-delete plugin. Used in: deletion audits, user recovery, GDPR compliance.","nullable":true,"example":"2025-10-01T12:00:00.000Z"},"createdAt":{"type":"string","format":"date-time","description":"User creation timestamp (automatic by mongoose timestamps). Immutable - cannot be modified after creation. Used in: sorting, growth statistics, auditing.","example":"2024-12-01T10:30:00.000Z"},"updatedAt":{"type":"string","format":"date-time","description":"Last modified timestamp of the user (automatic by mongoose timestamps). Updated on every save() - Reflects the last edit of any field. Used in: synchronization, cache invalidation, change detection.","example":"2025-10-20T15:45:30.000Z"},"lastAccess":{"type":"string","format":"date-time","description":"Timestamp of the last recorded access to any authenticated endpoint. Updated by: middleware m.getAction on each authenticated request. Different from lastSignInAt (the latter updates on every request, not just login). Used in: inactivity detection, engagement metrics.","example":"2025-10-23T10:22:15.000Z"}},"title":"User"},"example":{"_id":"63d7907cbe76403b35da70f","email":"nuevo_usuario@empresa.com","name":"Juan","lastname":"Pérez García","role":"gestor","status":true,"i18n":"es","emailVerified":false,"createdAt":"2025-02-12T10:30:00.000Z"}}}},"400":{"description":"Invalid request. Possible causes:\n- Email not provided\n- Save error (automatic rollback)","content":{"application/json":{"schema":{"type":"object","required":["status","message"],"properties":{"status":{"type":"integer","description":"HTTP status code","minimum":400,"maximum":599,"example":400},"message":{"type":"string","description":"Error code from the system.\n\nCommon error codes (see listado_errores_http.txt for complete list):\n- NO_TOKEN (401): JWT token not provided\n- TOKEN_NOT_VALID (401): JWT token is invalid or expired\n- NO_ADMIN_ROLE (401): User does not have admin privileges\n- INVALID_PARAMETERS (400): Request parameters are invalid\n- NOT_FOUND (404): Requested resource not found\n- ALREADY_EXIST (401): Resource with same identifier already exists\n- UTC_VALIDATION_FAILED (400): UTC timestamp validation failed\n- INTERNAL_ERROR (500): Unexpected server error occurred\n\nThe message is resolved by `handlerError.getErrorMessage(error)`.","example":"INVALID_PARAMETERS"}},"description":"Standard error response format used across all API endpoints.\n\nAll errors follow the pattern `{status: number, message: string}`.\nThe status code is repeated in both the HTTP response and the body.\n\nError messages are constants defined in the codebase and should be\nhandled on the client side with appropriate user-facing messages.","example":{"status":400,"message":"INVALID_PARAMETERS"},"title":"ErrorResponse"},"example":{"message":"FORM_DATA_NOT_VALID"}}}},"401":{"description":"Unauthorized (admin or dev role required)","content":{"application/json":{"schema":{"type":"object","required":["status","message"],"properties":{"status":{"type":"integer","description":"HTTP status code","minimum":400,"maximum":599,"example":400},"message":{"type":"string","description":"Error code from the system.\n\nCommon error codes (see listado_errores_http.txt for complete list):\n- NO_TOKEN (401): JWT token not provided\n- TOKEN_NOT_VALID (401): JWT token is invalid or expired\n- NO_ADMIN_ROLE (401): User does not have admin privileges\n- INVALID_PARAMETERS (400): Request parameters are invalid\n- NOT_FOUND (404): Requested resource not found\n- ALREADY_EXIST (401): Resource with same identifier already exists\n- UTC_VALIDATION_FAILED (400): UTC timestamp validation failed\n- INTERNAL_ERROR (500): Unexpected server error occurred\n\nThe message is resolved by `handlerError.getErrorMessage(error)`.","example":"INVALID_PARAMETERS"}},"description":"Standard error response format used across all API endpoints.\n\nAll errors follow the pattern `{status: number, message: string}`.\nThe status code is repeated in both the HTTP response and the body.\n\nError messages are constants defined in the codebase and should be\nhandled on the client side with appropriate user-facing messages.","example":{"status":400,"message":"INVALID_PARAMETERS"},"title":"ErrorResponse"},"example":{"message":"NO_TOKEN"}}}},"403":{"description":"Forbidden. Possible causes:\n- User is not an administrator\n- Plan does not allow creating more users","content":{"application/json":{"schema":{"type":"object","required":["status","message"],"properties":{"status":{"type":"integer","description":"HTTP status code","minimum":400,"maximum":599,"example":400},"message":{"type":"string","description":"Error code from the system.\n\nCommon error codes (see listado_errores_http.txt for complete list):\n- NO_TOKEN (401): JWT token not provided\n- TOKEN_NOT_VALID (401): JWT token is invalid or expired\n- NO_ADMIN_ROLE (401): User does not have admin privileges\n- INVALID_PARAMETERS (400): Request parameters are invalid\n- NOT_FOUND (404): Requested resource not found\n- ALREADY_EXIST (401): Resource with same identifier already exists\n- UTC_VALIDATION_FAILED (400): UTC timestamp validation failed\n- INTERNAL_ERROR (500): Unexpected server error occurred\n\nThe message is resolved by `handlerError.getErrorMessage(error)`.","example":"INVALID_PARAMETERS"}},"description":"Standard error response format used across all API endpoints.\n\nAll errors follow the pattern `{status: number, message: string}`.\nThe status code is repeated in both the HTTP response and the body.\n\nError messages are constants defined in the codebase and should be\nhandled on the client side with appropriate user-facing messages.","example":{"status":400,"message":"INVALID_PARAMETERS"},"title":"ErrorResponse"},"example":{"message":"PLAN_LIMIT_REACHED"}}}},"406":{"description":"Email already exists in the database.","content":{"application/json":{"schema":{"type":"object","required":["status","message"],"properties":{"status":{"type":"integer","description":"HTTP status code","minimum":400,"maximum":599,"example":400},"message":{"type":"string","description":"Error code from the system.\n\nCommon error codes (see listado_errores_http.txt for complete list):\n- NO_TOKEN (401): JWT token not provided\n- TOKEN_NOT_VALID (401): JWT token is invalid or expired\n- NO_ADMIN_ROLE (401): User does not have admin privileges\n- INVALID_PARAMETERS (400): Request parameters are invalid\n- NOT_FOUND (404): Requested resource not found\n- ALREADY_EXIST (401): Resource with same identifier already exists\n- UTC_VALIDATION_FAILED (400): UTC timestamp validation failed\n- INTERNAL_ERROR (500): Unexpected server error occurred\n\nThe message is resolved by `handlerError.getErrorMessage(error)`.","example":"INVALID_PARAMETERS"}},"description":"Standard error response format used across all API endpoints.\n\nAll errors follow the pattern `{status: number, message: string}`.\nThe status code is repeated in both the HTTP response and the body.\n\nError messages are constants defined in the codebase and should be\nhandled on the client side with appropriate user-facing messages.","example":{"status":400,"message":"INVALID_PARAMETERS"},"title":"ErrorResponse"},"example":{"message":"USER_ALREADY_EXIST"}}}}}}
>

</StatusCodes>