Import data into InterAction+ programmatically using the Tenant API
What is the Imports Service
The Imports Service lets an external application load data into InterAction+ over an API, instead of using the manual UI importer. Using the Tenant API, an authorized application can create and upload data import jobs for entities such as contacts, activities, relationships, and opportunities — the same import engine that powers imports today, driven programmatically.
At present, the Data Imports endpoints are the only Tenant API endpoints available for integration. The Tenant API itself is a secure, tenant-aware gateway built for unattended, server-to-server integrations — there is no interactive login step, so it is well suited to scheduled jobs and backend systems.
Why use it
The Imports Service is built for teams who need to bring data into InterAction+ on a recurring or automated basis, rather than uploading files by hand. Typical scenarios include:
- Nightly or scheduled synchronization of contacts, activities, or opportunities from an external CRM, HR system, or data warehouse.
- Bulk onboarding of firm data during a migration or system cutover.
- Backend jobs that generate a data extract and need it loaded into InterAction+ without a person in the loop.
Key benefits:
- Unattended by design — the OAuth 2.0 Client Credentials flow requires no user to log in, so imports can run from a server or scheduled job.
- Immediate or scheduled — run an import as soon as the file uploads, or schedule it for a specific future time.
- Governed like any other integration — each integration is registered as its own Tenant Client, scoped to a single tenant and attributed to a designated user, so activity is traceable and permissions stay controlled.
- Tenant- and region-aware — every call is scoped to your tenant and hosted region, so data stays within your tenant's boundary.
How it works
At a high level, using the Imports Service involves one-time setup, then a repeatable four-step call pattern for every file you import:
- Register (one-time). A tenant administrator registers a Tenant Client in CIM and receives a Client ID and Client Secret.
- Authenticate. Your application exchanges those credentials for a short-lived access token.
- Create an import. Your application calls the Imports API with the entity type and template to use, and receives back a fileId and a time-limited upload URL.
- Upload the file. Your application uploads the CSV file to that URL. The import then runs immediately, or at a scheduled time you specified.
The sections below walk through each part of this flow in order, followed by guidance specific to individual entity types.
One-time setup: Register a Tenant Client
Before an integration can authenticate, a tenant administrator must register a Tenant Client in CIM. A Tenant Client is a dedicated service account that represents your integration. It is scoped to a single tenant and acts under the identity and permissions of a designated user.
Register a Tenant Client
In CIM, go to Firm Settings > General Management > Data Import Settings and select Add Tenant Client. In the Add Tenant Client dialog (General tab), provide:
- Name — a human-readable label for the integration (for example, “Nightly Contact Import”). Required, maximum 60 characters.
- Description — an optional summary of what the integration does. Maximum 120 characters.
- Scopes — the permissions the client may request. Select Manage Data Imports (this corresponds to the tenant.dataimports.modify scope used in token requests).
- Designated User — the tenant user the integration will act as (see Assign a designated user below).
The Add Tenant Client dialog in CIM (Firm Settings > General Management > Data Import Settings).
Select Add Tenant Client to save. CIM issues a Client ID that uniquely identifies the integration. Record this value; you will need it when requesting an access token.
Generate a Tenant Client secret
On the Secrets tab for the Tenant Client, select Add Secret. Choose an expiry date — it must be a future date, no more than one year from today — then select Generate. After the secret expires it is no longer valid and a new one must be generated.
The Add Secret dialog — choose an expiry date, then Generate.
Important: The full Tenant Client secret is displayed only when it is generated. Copy it immediately and store it securely (for example, in a secrets manager or key vault). CIM retains only a hashed copy and a short display fragment for identification — the full value cannot be retrieved later. If the secret is lost, revoke it and generate a new one.
You can maintain more than one secret at a time and revoke individual secrets, which supports zero-downtime rotation: generate the new secret, deploy it to your application, verify it works, then revoke the old one.
Assign a designated user
Every Tenant Client is bound to a designated user in the tenant, selected in the Designated User field of the Add Tenant Client dialog. Because the Client Credentials flow has no interactive login, the designated user provides the identity and permission context under which the integration operates. The access token issued to the client carries this user's context, and data imports created through the API are attributed to that user.
Choose a designated user whose role and permissions match what the integration needs to do — for example, a user permitted to import data. If the designated user is later disabled, token requests for the client will fail, so use a stable, actively maintained account (a service or system account is recommended over a named individual).
Once registration is complete you will have a Client ID, a Client Secret, and a Tenant Client bound to a designated user with the Manage Data Imports scope. These are used to obtain access tokens as described next.
Authenticate
The Tenant API accepts OAuth 2.0 access tokens issued by the InterAction+ identity service. To obtain a token, your application makes a Client Credentials request to your tenant's authority URL.
Authority URL
Each tenant authenticates against its own authority URL. Use the authority URL for the region in which your tenant is hosted, replacing {tenantLabel} with your tenant's label:
| Region | Authority URL |
| US | https://us.apps.interaction.com/{tenantLabel}/auth/ |
| EU | https://eu.apps.interaction.com/{tenantLabel}/auth/ |
| AU | https://au.apps.interaction.com/{tenantLabel}/auth/ |
The token endpoint is the authority URL followed by connect/token. For example, a tenant with the label lncloud in the US region would use https://us.apps.interaction.com/lncloud/auth/connect/token (lncloud is illustrative — substitute your own tenant label).
Scope
Request the scope that matches the operations you intend to perform. In CIM this permission is shown as Manage Data Imports; in token requests it is named as follows, and is currently the only scope available for integration:
| Scope | Description |
| Manage Data Imports | Corresponds to tenant.dataimports.modify — the scope used for uploading data import files. |
The scope you request must be one that was granted to the Tenant Client during registration.
Request an access token
Send a POST request to the token endpoint with a form-encoded body containing the client credentials grant parameters.
The values below are illustrative. Replace lncloud with your tenant label and YOUR_CLIENT_ID / YOUR_CLIENT_SECRET with the credentials issued in CIM.
POST https://us.apps.interaction.com/lncloud/auth/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=tenant.dataimports.modifyA successful response returns a JSON payload containing the access token (tokens are shown truncated):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "tenant.dataimports.modify"
}| Field | Description |
| access_token | The bearer token to send on subsequent Tenant API calls. |
| expires_in | Token lifetime in seconds. Access tokens are valid for 3600 seconds (60 minutes). |
| token_type | Always Bearer. |
| scope | The scope(s) granted to the token. |
Token caching and refresh
Access tokens are valid for 60 minutes. Do not request a new token for every API call. Instead, cache the token and reuse it until shortly before it expires, then request a fresh one. A common pattern is to refresh when the token is within a minute or two of expiry, or to request a new token only after a call fails with 401 Unauthorized. Requesting a token per call is unnecessary, adds latency, and places avoidable load on the authority.
Create an import
Once you have an access token, call the Tenant API by sending the token in the HTTP Authorization header as a bearer token:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6...The Tenant API base URL is region-specific. Use the base URL for the region in which your tenant is hosted:
| Region | Tenant API base URL |
| US | https://tm-prod-us.apps.interaction.com/tenant-api |
| EU | https://tm-prod-eu.apps.interaction.com/tenant-api |
| AU | https://tm-prod-au.apps.interaction.com/tenant-api |
Endpoint paths are appended to the base URL. For example, in the US region the imports endpoint is
https://tm-prod-us.apps.interaction.com/tenant-api/dataimports/importsThe token you present must have been issued by the authority for the same tenant and region you are calling; a token from one tenant cannot be used against another. All Data Imports endpoints require a bearer token carrying the tenant.dataimports.modify scope, and request/response bodies are JSON.
Common request fields
Creating an import — whether immediate or scheduled — uses the same core fields. Field rules that differ by entity type are covered separately in Entity-specific guidance, below.
| Field | Type | Required | Description |
| entityType | integer (enum) | Yes | The type of record being imported. See Entity types. |
| fileName | string | Yes | Original source file name. Maximum 255 characters. |
| templateId | integer | Yes | Identifier of the import template (field mapping) to apply. Must be greater than 0 and match the entityType. Create the template in InterAction+ before referencing it here. |
| owningDirectoryGuid | GUID | Conditional | PersonContact / CompanyContact only — see Entity-specific guidance. |
| addToMyContactUserGuid | GUID | Conditional | PersonContact / CompanyContact only — see Entity-specific guidance. |
| companyOwningDirectoryGuid | GUID | Optional | PersonContact only — see Entity-specific guidance. |
Create an immediate import
Creates a data import that begins processing as soon as the file is uploaded.
| Property | Value |
| Method | POST |
| Path | /dataimports/imports |
| Authentication | Bearer token with scope tenant.dataimports.modify |
| Content-Type | application/json |
| Success | 201 Created |
Example (illustrative values — replace the tenant label, IDs, GUIDs, file names, and tokens with your own):
POST https://tm-prod-us.apps.interaction.com/tenant-api/dataimports/imports
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6...
Content-Type: application/json
{
"entityType": 3,
"fileName": "contacts.csv",
"templateId": 42,
"owningDirectoryGuid": "550e8400-e29b-41d4-a716-446655440001"
}
HTTP/1.1 201 Created
Content-Type: application/json
{
"fileId": "7f9c1e2a-3b4d-4e5f-8a9b-0c1d2e3f4a5b",
"uploadUrl": "https://<storage-host>/db/raw/personcontact_7f9c1e2a-....csv?...",
"uploadUrlExpiresOn": "2026-07-02T10:30:00Z"
}Create a scheduled import
Creates a data import that is processed at a future date and time. Takes the same fields as an immediate import (see Common request fields, above), plus one additional field:
| Field | Type | Required | Description |
| scheduledAt | date-time | Yes | UTC date and time (ISO 8601) at which the import should run. Must be a valid, non-default value, and a future time. |
| Property | Value |
| Method | POST |
| Path | /dataimports/imports/scheduled |
| Authentication | Bearer token with scope tenant.dataimports.modify |
| Content-Type | application/json |
| Success | 201 Created |
Example (illustrative values):
POST https://tm-prod-us.apps.interaction.com/tenant-api/dataimports/imports/scheduled
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6...
Content-Type: application/json
{
"entityType": 1,
"fileName": "activities.csv",
"templateId": 17,
"scheduledAt": "2026-07-05T02:00:00Z"
}The response is identical in shape to the immediate import response (fileId, uploadUrl, uploadUrlExpiresOn). Upload the file to uploadUrl the same way; the import runs at the scheduledAt time.
Upload the file
The import endpoints do not receive file content directly. Each call returns a fileId and a time-limited uploadUrl; your application then uploads the CSV file to that uploadUrl before it expires (see uploadUrlExpiresOn in the response).
The uploadUrl is a pre-signed Azure Blob Storage SAS URI. Upload the file by issuing an HTTP PUT of the raw file bytes to that URI before it expires. The request goes directly to Azure Storage, not to the Tenant API.
Uploading a data import file with the SAS URI returned by the import call.
Example (illustrative values — use the uploadUrl returned by the import call and send your CSV as the request body):
PUT https://<storage-host>/db/raw/personcontact_7f9c1e2a-...csv?sv=2024-11-
04&sp=cw&se=2026-07-16T18:00:00Z&sig=REDACTED
x-ms-blob-type: BlockBlob
Content-Type: text/csv
<raw bytes of contacts.csv>
HTTP/1.1 201 Created- Use the complete SAS URI exactly as returned by the import call, including its full query string — the signature is part of the URL.
- A successful upload returns 201 Created with an empty body; a missing, malformed, or expired SAS URI returns 403 Forbidden.
Entity-specific guidance
A small number of fields on the import request behave differently depending on the entity type you're importing. This section covers those differences; all other fields work the same across entity types.
PersonContact and CompanyContact imports
When importing contacts into a list or into a user's My Contacts, provide exactly one of the following two fields — not both, not neither:
- owningDirectoryGuid — specifies the list/folder the contacts are sourced into.
- addToMyContactUserGuid — specifies the user whose My Contacts list the contacts are added to.
For PersonContact imports specifically, you may optionally also provide:
- companyOwningDirectoryGuid — specifies the list/folder where a company contact is sourced when one is auto-created for a person contact whose named company can't be matched to the Firm List.
All other entity types
For Activity, Relationship, Opportunity, Address, Folder, SummaryFinancials, Matter, Task, Initiative, and Objective imports, omit owningDirectoryGuid, addToMyContactUserGuid, and companyOwningDirectoryGuid entirely — all three must be null.
Reference
Entity types
The entityType field accepts the following integer values:
| Value | Entity type |
| 1 | Activity |
| 2 | Relationship |
| 3 | PersonContact |
| 4 | CompanyContact |
| 5 | Opportunity |
| 6 | Address |
| 7 | Folder |
| 8 | SummaryFinancials |
| 9 | Matter |
| 10 | Task |
| 11 | Initiative |
| 12 | Objective |
Error responses
When a request cannot be processed, the API returns a standard HTTP status code. Validation failures return an application/problem+json body describing what went wrong; some input-related conditions may currently surface as 500 rather than 422.
| Status | Description |
| 400 Bad Request | The request could not be parsed — malformed JSON or a body that does not match the expected shape. |
| 401 Unauthorized | The token is missing, invalid, or expired (no Authorization header, a malformed token, or a token past its 60-minute lifetime). Request a new token. |
| 403 Forbidden | The token is valid but not permitted for this operation — it does not carry the tenant.dataimports.modify scope, or the client is not authorized for this endpoint. |
| 422 Unprocessable Entity | The request was well-formed but failed validation. A required field is missing or invalid — for example, fileName is empty or longer than 255 characters, templateId is not greater than 0. |
| 500 Internal Server Error | An unexpected error occurred while processing the request. |
Example validation error:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://tools.ietf.org/html/rfc4918#section-11.2",
"title": "Validation failed.",
"status": 422,
"errors": {
"FileName": [ "FileName is required." ]
}
}Best practices
- Cache access tokens. Reuse a token for its 60-minute lifetime and refresh shortly before expiry rather than requesting one per call.
- Store secrets securely. Keep the Client Secret in a secrets manager or key vault, never in source control. Rotate secrets before they expire using overlapping secrets to avoid downtime.
- Match the region. Use the authority URL and Tenant API base URL for the region where your tenant is hosted, and request tokens for your own tenant label.
- Prepare templates in advance. Create the import template in InterAction+ first and reference its templateId; the entityType you send must match the template.
- Respect the upload window. Upload the CSV to the returned uploadUrl before uploadUrlExpiresOn. If the URL expires, create the import again to obtain a fresh one.
- Handle 401 gracefully. Treat a 401 as a signal to refresh the token and retry once, rather than failing the whole job.