> ## Documentation Index
> Fetch the complete documentation index at: https://dynamo-csms.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Installer Authentication — Signup, Login, and Profile

> Register, log in, and manage installer accounts. Covers signup, login, password reset, identity verification, and profile management for field installers.

Installer accounts are separate from organization API keys. They are used by field technicians to register charge points and run commissioning workflows through the Dynamo CSMS Installer App. All installer endpoints return a short-lived JWT access token that you include in subsequent requests.

***

## Register an installer account

`POST /api/v1/installer/signup`

Creates a new installer account immediately, without requiring email confirmation. Use `signup-with-confirmation` if you want to enforce email verification before the account becomes active.

<ParamField body="email" type="string" required>
  Installer's email address. Used as the login identifier.
</ParamField>

<ParamField body="password" type="string" required>
  Must be at least 10 characters, containing at least one uppercase letter, one lowercase letter, and one digit.
</ParamField>

<ParamField body="first_name" type="string" required>
  Installer's given name.
</ParamField>

<ParamField body="last_name" type="string" required>
  Installer's family name.
</ParamField>

<ParamField body="company" type="string" required>
  Name of the installer's employer or contracting company.
</ParamField>

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/installer/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "j.smith@acme-ev.com",
    "password": "Tr0ub4dor&3",
    "first_name": "Jane",
    "last_name": "Smith",
    "company": "Acme EV Services"
  }'
```

**Response `201 Created`**

```json theme={null}
{
  "installer_id": "ins_7f3a1b2c",
  "email": "j.smith@acme-ev.com",
  "first_name": "Jane",
  "last_name": "Smith",
  "company": "Acme EV Services",
  "status": "active",
  "created_at": "2024-03-10T09:00:00Z"
}
```

***

## Register with email confirmation

`POST /api/v1/installer/signup-with-confirmation`

Same as `signup`, but the account is created in a `pending_verification` state. Dynamo CSMS sends a confirmation email; the installer must click the link before they can log in.

Accepts the same body as `POST /api/v1/installer/signup`.

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/installer/signup-with-confirmation \
  -H "Content-Type: application/json" \
  -d '{
    "email": "j.smith@acme-ev.com",
    "password": "Tr0ub4dor&3",
    "first_name": "Jane",
    "last_name": "Smith",
    "company": "Acme EV Services"
  }'
```

**Response `201 Created`**

```json theme={null}
{
  "installer_id": "ins_7f3a1b2c",
  "email": "j.smith@acme-ev.com",
  "status": "pending_verification",
  "message": "A confirmation link has been sent to j.smith@acme-ev.com."
}
```

***

## Log in

`POST /api/v1/installer/login`

Authenticates an installer and returns a JWT access token. Include this token as `Authorization: Bearer <access_token>` in all subsequent installer API calls.

<ParamField body="email" type="string" required>
  The installer's registered email address.
</ParamField>

<ParamField body="password" type="string" required>
  The installer's password.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dynamo-csms.com/api/v1/installer/login \
    -H "Content-Type: application/json" \
    -d '{
      "email": "j.smith@acme-ev.com",
      "password": "Tr0ub4dor&3"
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.dynamo-csms.com/api/v1/installer/login",
      json={"email": "j.smith@acme-ev.com", "password": "Tr0ub4dor&3"},
  )
  token = response.json()["access_token"]
  ```
</CodeGroup>

**Response `200 OK`**

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "installer_id": "ins_7f3a1b2c"
}
```

<ResponseField name="access_token" type="string">
  JWT token to use in the `Authorization: Bearer` header.
</ResponseField>

<ResponseField name="token_type" type="string">
  Always `"Bearer"`.
</ResponseField>

<ResponseField name="expires_in" type="integer">
  Token lifetime in seconds (default 3600 — 1 hour).
</ResponseField>

<ResponseField name="installer_id" type="string">
  The authenticated installer's ID.
</ResponseField>

***

## Log out

`POST /api/v1/installer/logout`

Invalidates the current access token server-side. After logging out, the token will be rejected on all subsequent requests.

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/installer/logout \
  -H "Authorization: Bearer INSTALLER_ACCESS_TOKEN"
```

**Response `204 No Content`**

***

## Request password reset

`POST /api/v1/installer/forgot-password`

Sends a password reset email to the specified address if an account exists. The response is always `200 OK` regardless of whether the email is registered, to prevent account enumeration.

<ParamField body="email" type="string" required>
  The email address associated with the installer account.
</ParamField>

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/installer/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email": "j.smith@acme-ev.com"}'
```

**Response `200 OK`**

```json theme={null}
{
  "message": "If an account with that email exists, a reset link has been sent."
}
```

***

## Reset password

`POST /api/v1/installer/reset-password`

Completes a password reset using the token from the email link.

<ParamField body="token" type="string" required>
  The reset token extracted from the password reset email link.
</ParamField>

<ParamField body="new_password" type="string" required>
  The new password. Must meet the same strength requirements as signup.
</ParamField>

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/installer/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "rst_4f8a2b1c9d3e7f0a",
    "new_password": "N3wS3cur3P@ss!"
  }'
```

**Response `200 OK`**

```json theme={null}
{
  "message": "Password updated successfully. Please log in with your new password."
}
```

***

## Get profile

`GET /api/v1/installer/profile`

Returns the authenticated installer's profile information.

```bash theme={null}
curl https://api.dynamo-csms.com/api/v1/installer/profile \
  -H "Authorization: Bearer INSTALLER_ACCESS_TOKEN"
```

**Response `200 OK`**

```json theme={null}
{
  "installer_id": "ins_7f3a1b2c",
  "email": "j.smith@acme-ev.com",
  "first_name": "Jane",
  "last_name": "Smith",
  "company": "Acme EV Services",
  "phone": "+1-555-0100",
  "status": "active",
  "identity_verified": true,
  "terms_accepted_at": "2024-03-10T09:05:00Z",
  "created_at": "2024-03-10T09:00:00Z"
}
```

***

## Update profile

`PUT /api/v1/installer/profile`

Updates the authenticated installer's profile. All fields are optional — only include the ones you want to change.

<ParamField body="first_name" type="string">
  Updated given name.
</ParamField>

<ParamField body="last_name" type="string">
  Updated family name.
</ParamField>

<ParamField body="company" type="string">
  Updated company name.
</ParamField>

<ParamField body="phone" type="string">
  Contact phone number in E.164 format (e.g. `"+1-555-0100"`).
</ParamField>

```bash theme={null}
curl -X PUT https://api.dynamo-csms.com/api/v1/installer/profile \
  -H "Authorization: Bearer INSTALLER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+1-555-0199",
    "company": "Acme EV Services Ltd."
  }'
```

**Response `200 OK`** — returns the full updated profile object (same schema as GET profile).

***

## Get verification status

`GET /api/v1/installer/verification-status`

Returns the current identity verification state for the installer account.

```bash theme={null}
curl https://api.dynamo-csms.com/api/v1/installer/verification-status \
  -H "Authorization: Bearer INSTALLER_ACCESS_TOKEN"
```

**Response `200 OK`**

```json theme={null}
{
  "installer_id": "ins_7f3a1b2c",
  "identity_verified": false,
  "verification_submitted_at": "2024-03-11T10:00:00Z",
  "verification_status": "under_review",
  "rejection_reason": null
}
```

<ResponseField name="identity_verified" type="boolean">
  `true` when identity verification has been approved.
</ResponseField>

<ResponseField name="verification_status" type="string">
  One of `not_submitted`, `under_review`, `approved`, `rejected`.
</ResponseField>

<ResponseField name="rejection_reason" type="string | null">
  Human-readable reason if status is `rejected`, otherwise `null`.
</ResponseField>

***

## Submit identity verification

`POST /api/v1/installer/verify-identity`

Submits identity documents for review. Verification is required before an installer can commission charge points in production.

<ParamField body="document_type" type="string" required>
  Type of identity document. One of: `passport`, `drivers_license`, `national_id`.
</ParamField>

<ParamField body="document_number" type="string" required>
  The document's identification number.
</ParamField>

<ParamField body="document_expiry" type="string" required>
  Document expiry date in `YYYY-MM-DD` format.
</ParamField>

<ParamField body="selfie_url" type="string" required>
  URL of an uploaded selfie image for liveness check. Upload the image to your storage first and provide the URL.
</ParamField>

<ParamField body="document_front_url" type="string" required>
  URL of the front-of-document image.
</ParamField>

<ParamField body="document_back_url" type="string">
  URL of the back-of-document image. Required for `drivers_license` and `national_id`.
</ParamField>

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/installer/verify-identity \
  -H "Authorization: Bearer INSTALLER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "document_type": "drivers_license",
    "document_number": "D123-4567-8901",
    "document_expiry": "2028-06-30",
    "selfie_url": "https://storage.example.com/selfie_jsmith.jpg",
    "document_front_url": "https://storage.example.com/dl_front_jsmith.jpg",
    "document_back_url": "https://storage.example.com/dl_back_jsmith.jpg"
  }'
```

**Response `202 Accepted`**

```json theme={null}
{
  "message": "Identity verification submitted. Review typically takes 1–2 business days.",
  "verification_status": "under_review",
  "submitted_at": "2024-03-11T10:00:00Z"
}
```

***

## Accept terms of service

`POST /api/v1/installer/accept-terms`

Records that the installer has accepted the current Dynamo CSMS Terms of Service. This must be called before the installer can register charge points.

<ParamField body="terms_version" type="string" required>
  The version string of the terms being accepted (e.g. `"2024-01"`). You can find the current version in your Dynamo CSMS dashboard.
</ParamField>

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/installer/accept-terms \
  -H "Authorization: Bearer INSTALLER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"terms_version": "2024-01"}'
```

**Response `200 OK`**

```json theme={null}
{
  "message": "Terms of service accepted.",
  "terms_version": "2024-01",
  "accepted_at": "2024-03-10T09:05:00Z"
}
```

***

## Error responses

| Status | Code                  | Meaning                                            |
| ------ | --------------------- | -------------------------------------------------- |
| `400`  | `validation_error`    | One or more required fields are missing or invalid |
| `401`  | `invalid_credentials` | Email or password is incorrect                     |
| `401`  | `token_expired`       | The access token has expired — log in again        |
| `403`  | `account_pending`     | Account not yet email-confirmed                    |
| `403`  | `identity_required`   | Identity verification needed before this action    |
| `403`  | `terms_required`      | Terms of service must be accepted first            |
| `409`  | `email_taken`         | An account with this email already exists          |
| `422`  | `weak_password`       | Password does not meet strength requirements       |
