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

# Provisioning

> Set up an organization, its system integrations and domain mappings through the Twine Public API

Provisioning is setting up a customer in Twine entirely through the API: creating the [Organization](https://docs.twine.se/platform/organization.md), connecting its systems as [System Integrations](https://docs.twine.se/platform/system-integrations.md), and linking them with [Domain Mappings](https://docs.twine.se/platform/domain-mappings.md) that decide when data moves. Vendor customers typically do this once per customer they onboard.

The steps below are the usual order. Each one needs ids returned by the previous one.

> [!NOTE]
>
> Creating an organization requires `create` permission on the organization the token belongs to. Firing a sync trigger requires admin permission on the domain mapping or on its source system integration. Neither is part of the default API service account role; ask Twine to grant them to the role you provision with.

## 1. Create the organization

`POST /v1/org` creates a new organization as a child of the organization your token belongs to.

Every other provisioning endpoint lives under `/v1/org/...` and acts on **the organization of the token making the request**, not on an id in the URL. To configure the new organization, you therefore need a token that belongs to it. Ask for one in the same request by adding a role with `create_in` set to `child` and `create_api_refresh_token` set to `true`:

```json theme={null}
{
  "name": "AcmeCorp Inc.",
  "vat": "SE556677889901",
  "roles": [
    {
      "name": "Provisioning",
      "admin": true,
      "create_in": "child",
      "create_api_refresh_token": true
    }
  ]
}
```

The response carries the refresh token under `roles[].tokens[]`, with its `jwt` and `jti`. This is the only time the refresh token is readable, so store it in your own secret store straight away. Exchange it for an access token as described in [Authentication](https://docs.twine.se/api-reference/authentication.md), and use that access token for the remaining steps.

> [!TIP]
>
> Set `suppression_ends_at` (and optionally `suppression_reason`) to keep automatic circuit breakers from tripping on the new organization while it is being onboarded, when error rates and large changes are expected.

## 2. Create the system integrations

A system integration is one connection to one system. Look up the `system_id` of each system you need with `GET /v1/systems`, then create an integration for each with `POST /v1/org/system-integrations`.

### The Twine system integration

Almost every organization needs a system integration for **Twine** itself. It is the side of a domain mapping that stands for data held in Twine, so it is what lets you read data synced from a source system through the API, and what replicates data you [push through the API](https://docs.twine.se/api-reference/pushing-data.md) on to a target system. It takes no credentials:

```json theme={null}
{
  "system_id": "<id of the twine system>",
  "system_custom": { "slug": "twine" }
}
```

Creating an organization does not create this integration for you.

### External systems

For an external system, send its credentials in `auth` and its system-specific settings in `system_custom`. Both are discriminated by `slug`, and the fields each system accepts are listed in the endpoint reference.

```json theme={null}
{
  "system_id": "<id of the flex_hrm system>",
  "description": "Flex HRM production",
  "auth": {
    "slug": "flex_hrm",
    "api_client_id": "integration-user@acme.example",
    "api_client_secret": "<password>",
    "api_endpoint_url": "https://acme.example-flexhosting.se/flexhrm"
  },
  "system_custom": { "slug": "flex_hrm" },
  "create_default_mappings": true
}
```

Pass `template_id` to create the integration from a template, or `create_default_mappings` to start from the system's default property mappings.

If your customer holds the credentials rather than you, leave `auth` out and [send them a credentials link](#letting-your-customer-enter-credentials) once the integration exists.

### Letting your customer enter credentials

`POST /v1/org/system-integrations/{id}/credentials-link` returns a `url` your customer opens to enter the integration's credentials themselves, without a Twine login. You never see what they enter. Systems that sign in through the vendor send the customer there instead.

The link collects credentials only. For a system whose API can live on any host (Flex HRM, Agda PS, S3, SFTP), where the integration connects is yours to set, before you create the link: `PATCH /v1/org/system-integrations/{id}` with `api_endpoint_url` in `auth` (Flex HRM, Agda PS) or the host in `system_custom` (S3, SFTP). Until then the link request answers `422`. Your customer sees that endpoint or host on the form, read-only, so they can tell you if it is not theirs.

The link is valid until `expires_at` (5 days) and can be submitted once. Create a new one if it expires, or whenever the credentials need to be entered again. Creating a link requires admin permission on the system integration, since whoever holds it can set the credentials.

Stored secrets are never returned by the API. See [Changing credentials and endpoints](#changing-credentials-and-endpoints) before updating an integration later.

## 3. Create the domain mappings

A domain mapping moves one domain of data from a source system integration to a target system integration, for example employees from an HR system into Twine. Create one with `POST /v1/org/domain-mappings`:

```json theme={null}
{
  "source_system_integration_id": "<flex_hrm integration id>",
  "target_system_integration_id": "<twine integration id>",
  "from_domain": "employee",
  "to_domain": "employee",
  "sync_triggers": [
    { "cron_expression": "0 2 * * *", "enabled": true }
  ]
}
```

The [sync trigger](https://docs.twine.se/platform/sync-triggers.md) decides when the mapping runs. There are two common setups:

* **Scheduled.** Give the trigger a `cron_expression` and set `enabled` to `true`. `enabled` defaults to `false`, so a trigger created without it does not run until it is enabled.
* **Manual.** Leave `cron_expression` out. The trigger then only runs when you fire it (see the next step). This suits initial loads, testing, and migrations where you want to control exactly when data moves.

Only the first entry in `sync_triggers` is used when creating a domain mapping. Add `conditions` to filter out entities this mapping should not process.

## 4. Run a sync

A scheduled trigger runs on its own. To run a sync now, whether the trigger is scheduled or manual, fire it:

```
POST /v1/org/domain-mappings/{domain_mapping_id}/sync-triggers/{sync_trigger_id}/fire
```

The response holds the `job_id` of the sync. Follow its progress with `GET /v1/org/jobs/{job_id}`. If a sync is already running for the same system integration and domain, the request is answered with `409 Conflict`; retry once that sync has finished.

## Changing credentials and endpoints

Update an integration with `PATCH /v1/org/system-integrations/{id}`. Only the fields you send with a value change. A field left out, or sent as `null`, keeps its stored value, and this holds inside `auth` and `system_custom` too. You can therefore rename an integration or change one setting without knowing its credentials.

Writing credentials requires admin permission on the system integration. A role without it can update everything else.

> [!NOTE]
>
> `PUT /v1/org/system-integrations/{id}` is deprecated. It requires `auth` and `system_custom` in every request; a `null` in either keeps the stored value. Use `PATCH` for new integrations.

> [!WARNING]
>
> **Changing an integration's endpoint clears its stored secrets.** When an update changes `api_endpoint_url`, the stored `api_key` and `api_client_secret` are removed unless they are sent in the same request, whether with new values or the current ones. The same applies when the S3 or SFTP host in `system_custom` changes.
>
> This is a security measure against credential theft. Without it, anyone allowed to edit the integration, even without access to its secrets, could point the endpoint at a server they control and simply wait: the next sync would send your customer's credentials straight to them. Clearing the secrets means a new endpoint only ever receives credentials from someone who already holds them.
>
> Because the API never returns stored secrets, you cannot read them back afterwards. Either send the endpoint and its credentials together, or, if your customer holds the credentials, change the endpoint first and then send them a new [credentials link](#letting-your-customer-enter-credentials).

Only a real change of endpoint clears anything. Sending the current URL again, or the same URL with different surrounding whitespace, a trailing slash, or different letter case in the scheme and host, keeps the secrets. `api_client_id` is always kept, since it identifies an account rather than proving access to it, and so are webhook secrets, which authenticate requests coming into Twine rather than going out.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.