# Settings and features

Canonical page: https://docs.ovok.com/settings-and-features

Ovok gives each project two kinds of controls. **Features** switch platform capabilities on for the whole project. **Settings** configure how a particular user flow behaves. Knowing which one you need helps avoid changing a project-wide capability when you only mean to change one audience's sign-in or registration flow.

## Feature or setting? [#feature-or-setting]

A **feature** is a capability the platform turns on for your project: automations, scheduled jobs, email sent from automations, live subscriptions, all-or-nothing batch writes, AI, and document-reading operations. When a feature-gated capability is off, it is unavailable to everyone in the project, whatever their role.

A **setting** is a rule you choose for how your project behaves for its users: who may sign in, register, or be invited; where links in your emails point; and which access policy a new practitioner receives. If a setting is off, the affected flow refuses one audience while other project operations continue to work.

> **Features answer “Can my project do this at all?” Settings answer “How should my project behave?”**

### Which one do I need? [#which-one-do-i-need]

Ask these questions:

1. If I turn it off, does an operation stop existing for everyone (a feature), or does one group get refused in one flow (a setting)?
2. Is it a name from a fixed list that is either on or absent (a feature), or a named value with a default (a setting)?
3. Does the platform provision it (a feature), or can my project admins change it as the app evolves (a setting)?

| | Feature | Setting |
| --- | --- | --- |
| Shape | Names from a fixed list; a listed feature is on. | Named values, such as a Boolean or text value; every supported key has a default. |
| Read | `GET /v1/projects/me/features` returns `{ features: [...] }`. | `GET /v1/project/settings` returns every key: what is saved, and what Ovok applies (`effectiveSettings`). Unset text reads as `null`. |
| Write | `PATCH /v1/projects/me/features` replaces the **whole list**. | `PUT /v1/project/settings/:key` changes one Boolean; `PUT /v1/project/settings/values/:key` changes one text value. |
| Who | Any practitioner can read; a project admin can write. | Any practitioner can read; a project admin can write. |
| When off | The feature-gated capability is unavailable to everyone in the project. | The affected flow refuses its audience; other project operations keep working. |
| Usually set | When the project is created. | By admins as the app evolves. |
| Inheritance | None. | On/off values do not inherit. App URLs can fall back to the parent project. |
| Concurrent edit | A write can return `409`; read again, reapply your change, and retry. | A write can return `409`; read again, reapply your change, and retry. |

For the authenticated examples below, set `OVOK_TOKEN` to a practitioner access token for the current project. Read requests are available to practitioners; write requests require a project admin.

## Project features [#project-features]

The feature API reads and writes the current project's complete feature list. For example, to add `cron`, first read the current list, add `cron`, then send the entire updated list. A `PATCH` is a replacement, not an add operation: omitting a feature turns it off.

```bash
curl --get 'https://api.sandbox.ovok.com/v1/projects/me/features' \
  --header "Authorization: Bearer ${OVOK_TOKEN}"
```

| Response field | Example | Meaning |
| --- | --- | --- |
| `features` | `bots`, `cron`, `email`, `transaction-bundles`, `websocket-subscriptions` | Complete list of feature names currently enabled for this project. |

```bash
curl --request PATCH \
  --url 'https://api.sandbox.ovok.com/v1/projects/me/features' \
  --header "Authorization: Bearer ${OVOK_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data '{"features":["bots","cron","email","transaction-bundles","websocket-subscriptions","ai"]}'
```

The available feature names include:

| Feature | Enables |
| --- | --- |
| [`bots`](/settings-and-features/features/bots) | Automations. |
| [`cron`](/settings-and-features/features/cron) | Scheduled jobs. Add `bots` as well to run automations on a schedule. |
| [`email`](/settings-and-features/features/email) | Email sent by automations. It does not control the emails Ovok sends itself. |
| [`websocket-subscriptions`](/settings-and-features/features/websocket-subscriptions) | Live updates through WebSocket subscriptions. |
| [`transaction-bundles`](/settings-and-features/features/transaction-bundles) | All-or-nothing FHIR transaction bundles. Without it, a transaction bundle runs as a batch. |
| [`ai`](/settings-and-features/features/ai) | The AI operation, called with your own model key. |
| [`aws-comprehend`](/settings-and-features/features/aws-comprehend) | Accepted and stored; no operation depends on it today. Comprehend Medical analysis is an option of `aws-textract`. |
| [`aws-textract`](/settings-and-features/features/aws-textract) | Document-reading and text-extraction operations. Needs S3 file storage on the platform. |
| [`google-auth-required`](/settings-and-features/features/google-auth-required) | Require Google sign-in where the platform's standard sign-in applies. |
| [`graphql-introspection`](/settings-and-features/features/graphql-introspection) | Accepted and stored; GraphQL introspection is currently controlled platform-wide. |
| [`validate-terminology`](/settings-and-features/features/validate-terminology) | Rejects coded values that break a required terminology binding when a resource is written. |

### Edit the feature list with care [#edit-the-feature-list-with-care]

`PATCH` replaces the whole list, and the 409 only protects against a write that overlaps yours inside the same request. If two admins each read the list, change it, and send it back, the second write silently overwrites the first. Read the list immediately before you write it, and send every feature you want to keep.

## Project settings [#project-settings]

`GET /v1/project/settings` returns every setting of the current project. The path is **singular**: `/project/settings`.

| Field | Holds | Use it to |
| --- | --- | --- |
| `effectiveSettings` | Each on/off switch as Ovok applies it: the saved value, else the default, and `false` while the switch lacks a setting it depends on. | **Decide whether a flow works.** |
| `overrides` | Each on/off switch's saved value, or `null` when none is saved. | See what an admin chose. |
| `settings` | Each on/off switch, `true` only when saved as `true`. Kept for older clients. | Nothing new: an unset switch reads `false` here even when it is on by default. |
| `values` | Each text setting: app URLs and default access policies, `null` when unset. | Read app URLs and policies. |

To change a Boolean, send `{"enabled": true}` or `{"enabled": false}` to `PUT /v1/project/settings/:key`. To set a text value, send `{"value": "..."}` to `PUT /v1/project/settings/values/:key`. Send `{"value": null}` to remove a stored text value.

```bash
curl --get 'https://api.sandbox.ovok.com/v1/project/settings' \
  --header "Authorization: Bearer ${OVOK_TOKEN}"
```

```bash
curl --request PUT \
  --url 'https://api.sandbox.ovok.com/v1/project/settings/PATIENT_REGISTRATION_ENABLED' \
  --header "Authorization: Bearer ${OVOK_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data '{"enabled":false}'
```

```bash
curl --request PUT \
  --url 'https://api.sandbox.ovok.com/v1/project/settings/values/PRACTITIONER_APP_URL' \
  --header "Authorization: Bearer ${OVOK_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data '{"value":"https://app.example.com"}'
```

### Defaults and dependencies [#defaults-and-dependencies]

A switch with no saved value uses its default. Some switches also need another setting: without it, the switch is saved as on but does nothing, and `effectiveSettings` shows `false`. **Read `effectiveSettings`, not `settings`, to know whether a flow works**, and set what a switch needs before you turn it on.

| Setting | Default when unset | Needs, or `effectiveSettings` is `false` |
| --- | --- | --- |
| [`PATIENT_LOGIN_ENABLED`](/settings-and-features/settings/patient-login-enabled) | On | — |
| [`PRACTITIONER_LOGIN_ENABLED`](/settings-and-features/settings/practitioner-login-enabled) | On | — |
| [`PATIENT_INVITATION_ENABLED`](/settings-and-features/settings/patient-invitation-enabled) | On | `DEFAULT_PATIENT_ACCESS_POLICY` set. |
| [`PRACTITIONER_INVITATION_ENABLED`](/settings-and-features/settings/practitioner-invitation-enabled) | On | — |
| [`RELATED_PERSON_LOGIN_ENABLED`](/settings-and-features/settings/related-person-login-enabled) | On | — |
| [`RELATED_PERSON_INVITATION_ENABLED`](/settings-and-features/settings/related-person-invitation-enabled) | On | — |
| [`PATIENT_REGISTRATION_ENABLED`](/settings-and-features/settings/patient-registration-enabled) | On | `DEFAULT_PATIENT_ACCESS_POLICY` naming an AccessPolicy of the project. |
| [`PRACTITIONER_REGISTRATION_ENABLED`](/settings-and-features/settings/practitioner-registration-enabled) | Off | [`DEFAULT_PRACTITIONER_ACCESS_POLICY`](/settings-and-features/settings/default-practitioner-access-policy) naming an AccessPolicy of the project. |
| [`CLINICIAN_INVITE_ON_BUSINESS_EMAIL`](/settings-and-features/settings/clinician-invite-on-business-email) | Off | Patient registration in effect, and `DEFAULT_PRACTITIONER_ACCESS_POLICY`. |

`effectiveSettings` does not check app URLs or email templates. Invitations also need an app URL (`PATIENT_APP_URL` or `PRACTITIONER_APP_URL`, which can come from the parent project), or they answer `409 app_url_not_configured`. `CLINICIAN_INVITE_ON_BUSINESS_EMAIL` also needs both app URLs and the `CLINICIAN_SIGNUP_INVITE` email template; without them, the person is registered as a patient and the caller is not told.

### Settings are not permissions [#settings-are-not-permissions]

An auth setting controls whether a sign-in, registration, or invitation flow can run. It does not define what a signed-in user can read or change; that is the access policy's job.

Turning a login switch off stops new sign-ins. Once the login-switch change is deployed, it will also stop token renewals. A token already issued can remain valid until it expires, for up to 60 minutes.

Registration and invitation switches also need their corresponding setup:

- Patient registration requires the project's default patient AccessPolicy.
- Practitioner registration requires [`DEFAULT_PRACTITIONER_ACCESS_POLICY`](/settings-and-features/settings/default-practitioner-access-policy).
- Invitations need the matching app URL ([`PATIENT_APP_URL`](/settings-and-features/settings/patient-app-url) or [`PRACTITIONER_APP_URL`](/settings-and-features/settings/practitioner-app-url)) and the applicable default AccessPolicy. App URLs can fall back to the parent project. If no URL resolves, the invite request returns `409 app_url_not_configured`.

The settings response reports values stored on the current project. An app URL shown as `null` there may still resolve from a parent project for link generation.

## Quick reference [#quick-reference]

| I want to… | Switch |
| --- | --- |
| Run automations | Feature `bots`. |
| Run automations on a schedule | Features `bots` and `cron`. |
| Send email from an automation | Feature `email`. |
| Write several records all-or-nothing | Feature `transaction-bundles`. |
| Get live updates | Feature `websocket-subscriptions`. |
| Require Google sign-in | Feature `google-auth-required`. |
| Stop patients registering themselves | Setting `PATIENT_REGISTRATION_ENABLED` = `false`. |
| Pause practitioner sign-in | Setting `PRACTITIONER_LOGIN_ENABLED` = `false`. |
| Let admins invite colleagues | Setting `PRACTITIONER_INVITATION_ENABLED`, plus `PRACTITIONER_APP_URL` and a default practitioner AccessPolicy. |
| Let practitioners register themselves | Setting `PRACTITIONER_REGISTRATION_ENABLED` = `true`, plus `DEFAULT_PRACTITIONER_ACCESS_POLICY`. |
| Make emailed links open my app | Set `PATIENT_APP_URL` and/or `PRACTITIONER_APP_URL`. |
| Invite clinicians who register with a work email | [`CLINICIAN_INVITE_ON_BUSINESS_EMAIL`](/settings-and-features/settings/clinician-invite-on-business-email), plus a default practitioner AccessPolicy and both app URLs. |
| Customise the emails Ovok sends | [Email templates](/email-templates). |
| Let patients share data | [`PATIENT_SHARING_ENABLED`](/settings-and-features/settings/patient-sharing-enabled) at `/v1/patient-sharing/settings`. |
| Let patients invite carers | [`PATIENT_SHARING_ENABLED`](/settings-and-features/settings/patient-sharing-enabled), [`RELATED_PERSON_INVITATION_ENABLED`](/settings-and-features/settings/related-person-invitation-enabled), [`RELATED_PERSON_LOGIN_ENABLED`](/settings-and-features/settings/related-person-login-enabled) and `PATIENT_APP_URL`. See [Carers](/authentication/carers). |

## Reserved settings [#reserved-settings]

[`MAILING_ENABLED`](/settings-and-features/settings/mailing-enabled) and [`CONTENT_ENABLED`](/settings-and-features/settings/content-enabled) (which the Console sets when you enable the [CMS](/cms)) are accepted settings keys but do not control an active Ovok flow yet. Treat them as reserved; use the `email` feature to allow email sent from automations.
