> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hopae.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit Activation

> Submit activation data for one or more providers in a single call.

Submits activation inputs for one or more providers on the app. Direct providers activate immediately, registration providers are queued for Hopae operator review, and contract providers return a `contract_activation_required` error directing the customer to the support contact. The response carries a per-provider result keyed by `providerId` — partial success is normal.

## Request

<ParamField header="Authorization" type="string" required>
  `Bearer <API_KEY>` from Console > Developers > Workspace API Keys.
</ParamField>

<ParamField header="Content-Type" type="string">
  `application/json`
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Optional. When provided, the request is idempotent across retries within the standard idempotency window.
</ParamField>

### Path parameters

<ParamField path="client_id" type="string" required>
  Client identifier of the app.
</ParamField>

### Body

<ParamField body="providers" type="object" required>
  Map of `providerId` to the field values for that provider. The value is a `{ fieldId: value }` map; each value is a `string` or `boolean`. For direct providers, pass an empty object `{}`. For contract providers, the request body for that provider is ignored — the response is `contract_activation_required` (unless you set `demo: true` in sandbox).

  ```json theme={null}
  {
    "providers": {
      "mitid":    { "legalCompanyName": "Acme", "registrationNumber": "12345678", "...": "..." },
      "passport": {}
    }
  }
  ```

  The set of required fields for a registration provider is whatever `requirements` returns from [Get Providers](/api-reference/workspace/get-providers) with `?include=activation`. Common field IDs include `legalCompanyName`, `displayName`, `logo`, `registrationNumber`, `registeredAddress`, `vatNumber`, `serviceDescription`, `websiteUrl`, `contactEmail`, `contactPhone`, `privacyPolicyUrl`, `termsOfServiceUrl`, and provider-specific fields (e.g., `userDataLevel` for SPID, `bankName` / `useSsn` / `gdprArticle` for BankID Norway, `legalRepName` for iDIN).
</ParamField>

<ParamField body="persist" type="boolean" default="true">
  When `true` (default), common fields are mirrored into the app's `companyInfo` so subsequent activations for the same app prefill them. Set to `false` to skip the `companyInfo` write — the activation ticket itself is still written.
</ParamField>

<ParamField body="demo" type="boolean" default="false">
  **Sandbox only.** Set to `true` to skip operator review while testing: registration and contract providers auto-approve to `activated` about 1 second after submission, so you can exercise the full activated flow end-to-end. Omit it (or send `false`) to run the real review flow in sandbox. Sending `true` outside sandbox returns `400 VALIDATION_INVALID_PARAMETER`. Combine with `persist: false` so test values are not written into the app's shared `companyInfo`.
</ParamField>

### Field validation

Field values are checked against the provider's registration spec:

* **Missing required fields** — fall back to the value cached in `companyInfo` (if any). If still empty, the provider's ticket is recorded with `status: 'rejected'` and `rejection.reason: 'missing_required_fields'`. Other providers in the same call are unaffected.
* **Empty strings, whitespace-only values, and `null`** are treated as missing.
* Field `type` (`text`, `email`, `url`, `checkbox`, `select`) and option lists are advisory — server-side validation is currently scope-of-fields only. Provide well-formed values.

## Response

`200 OK` with a JSON object keyed by `providerId`. Each value is one of three shapes:

### Success / queued / rejected

```json theme={null}
{ "id": "act_01HXYZ...", "status": "<ActivationStatus>", "updatedAt": "<ISO-8601>" }
```

`status` is the resulting ticket status — typically:

* `activated` for direct providers
* `activation_requested` for successful registration submissions
* `rejected` for registration submissions that failed field validation (the `activation.rejection` block on [Get Providers](/api-reference/workspace/get-providers) carries the missing field IDs)

In sandbox, tickets are real, so `id` is an `act_...` value. Sandbox otherwise behaves exactly like production — unless you submit with `demo: true`, which makes registration and contract providers return `activation_processing` and then auto-approve to `activated` about 1 second later.

### Contract provider

```json theme={null}
{
  "error": "contract_activation_required",
  "message": "ftn activation requires off-API contract onboarding. Please email support@hopae.com to begin activation.",
  "contact": "support@hopae.com"
}
```

No ticket is written. The customer should email the contact to start the off-API onboarding flow.

## Errors

These conditions abort the whole request — none of the requested providers are submitted.

| Status | Body                           | When                                                                                                                                                                                           |
| ------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `VALIDATION_INVALID_PARAMETER` | A requested provider is not available in the current `DEPLOYMENT_ENV` (e.g., a production-only provider submitted from sandbox), **or** `demo: true` was sent outside the sandbox environment. |
| `401`  | `INVALID_API_KEY`              | Missing or malformed `Authorization` header.                                                                                                                                                   |
| `403`  | `RESOURCE_FORBIDDEN`           | The API key's organization does not own `client_id`.                                                                                                                                           |
| `404`  | `RESOURCE_NOT_FOUND`           | No app matches `client_id`, or the requested provider id does not exist.                                                                                                                       |
| `409`  | `RESOURCE_CONFLICT`            | A previous submission for the same provider is currently `activation_processing` and cannot be overwritten, **or** a concurrent submit lost the optimistic-concurrency race. Retry.            |

## Concurrent submissions

The activation pointer on the app document is updated with optimistic concurrency. A losing writer is retried automatically once; if the second attempt also loses, the request fails with `409 RESOURCE_CONFLICT`. Submit the same provider again to retry — the previous submission's data is overwritten unless the prior ticket was already `activation_processing` (which holds the slot until the operator finishes review).

<RequestExample>
  ```bash theme={null}
  curl -X POST \
    -H "Authorization: Bearer sk_workspace_test_..." \
    -H "Content-Type: application/json" \
    -d '{
      "providers": {
        "mitid": {
          "legalCompanyName": "Acme Corp",
          "registrationNumber": "12345678",
          "serviceDescription": "Customer identity verification for KYC onboarding.",
          "displayName": "Acme",
          "contactEmail": "compliance@acme.com"
        },
        "passport": {},
        "ftn":      {}
      }
    }' \
    "https://sandbox.api.hopae.com/connect/v1/apps/HqTRDIYH/activation"
  ```
</RequestExample>

<ResponseExample>
  ```json Production theme={null}
  {
    "mitid": {
      "id": "act_01HXYZ2KQ8M0E3J1N9V0P5T8YR",
      "status": "activation_requested",
      "updatedAt": "2026-05-27T10:00:00.000Z"
    },
    "passport": {
      "id": "act_01HXYZ2KQ8M0E3J1N9V0P5T8YS",
      "status": "activated",
      "updatedAt": "2026-05-27T10:00:00.000Z"
    },
    "ftn": {
      "error": "contract_activation_required",
      "message": "ftn activation requires off-API contract onboarding. Please email support@hopae.com to begin activation.",
      "contact": "support@hopae.com"
    }
  }
  ```

  ```json Sandbox (demo: true) theme={null}
  {
    "mitid":    { "id": "act_01HXYZ2KQ8M0E3J1N9V0P5T8YR", "status": "activation_processing", "updatedAt": "2026-05-27T10:00:00.000Z" },
    "passport": { "id": "act_01HXYZ2KQ8M0E3J1N9V0P5T8YS", "status": "activated",             "updatedAt": "2026-05-27T10:00:00.000Z" },
    "ftn":      { "id": "act_01HXYZ2KQ8M0E3J1N9V0P5T8YT", "status": "activation_processing", "updatedAt": "2026-05-27T10:00:00.000Z" }
  }
  ```

  ```json Registration rejection (missing fields) theme={null}
  {
    "mitid": {
      "id": "act_01HXYZ2KQ8M0E3J1N9V0P5T8YR",
      "status": "rejected",
      "updatedAt": "2026-05-27T10:00:00.000Z"
    }
  }
  ```
</ResponseExample>
