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

# Create an Integration

> Register a custom integration, optionally with its actions and credential

Register a custom integration under your organization. Optionally supply `actions[]` and a single `credential` inline, so an integration can be authored in one call.

Returns `versionId`, which you pass to [Add a Credential](/api-reference/v1-integration/create-credential).

Requires the **Integration: Full** permission at organization level.

<Note>
  `integration.providerId` must belong to your organization. [Create a provider](/api-reference/v1-integration/create-provider) first if you do not have one.
</Note>

## Headers

<ParamField header="x-service-key" type="string" required>
  Your API authentication key
</ParamField>

## Body Parameters

The integration is nested under an `integration` key.

<ParamField body="integration" type="object" required>
  The integration to create. Fields below.
</ParamField>

<ParamField body="integration.name" type="string" required>
  Integration name. Maximum 255 characters.
</ParamField>

<ParamField body="integration.icon" type="string" required>
  Integration icon as a base64-encoded SVG (or PNG) — a plain string, with no `data:` prefix. Maximum 65535 characters.
</ParamField>

<ParamField body="integration.category" type="string[]" required>
  Integration categories. Between 1 and 5 entries, for example `machine_learning_and_ai_services` or `collaboration_tools`.
</ParamField>

<ParamField body="integration.providerId" type="string" required>
  The provider this integration is authored under, from [Create a Provider](/api-reference/v1-integration/create-provider).
</ParamField>

<ParamField body="integration.description" type="string">
  Integration description. Maximum 2048 characters.
</ParamField>

<ParamField body="integration.requiredPlan" type="string" default="core">
  Minimum plan an organization needs to connect this integration — `core`, `pro` or `scale`.
</ParamField>

<ParamField body="integration.status" type="string">
  Integration status — `active`, `inactive`, `soon` or `deprecated`.
</ParamField>

<ParamField body="integration.isNew" type="boolean">
  Show the "new" badge on the integration tile.
</ParamField>

<ParamField body="integration.isPopular" type="boolean">
  Show the "popular" badge on the integration tile.
</ParamField>

<ParamField body="integration.isBeta" type="boolean">
  Show the "beta" badge on the integration tile. Accepted on create but not currently echoed back in the response.
</ParamField>

<ParamField body="integration.actions" type="object[]">
  Actions to create alongside the integration. Maximum 50. See [Action object](#action-object).
</ParamField>

<ParamField body="integration.credential" type="object">
  A credential to create alongside the integration. Same shape as the body of [Add a Credential](/api-reference/v1-integration/create-credential).
</ParamField>

<Note>
  Your organization is always taken from the API key — it cannot be set in the body. Nothing here is workspace-scoped.
</Note>

## Action object

<ParamField body="name" type="string" required>
  Action name. Maximum 255 characters.
</ParamField>

<ParamField body="endpoint" type="string" required>
  The external endpoint this action talks to. Metadata only — `code` is what actually executes. Maximum 2048 characters.
</ParamField>

<ParamField body="inputs" type="object" required>
  The action's input schema, keyed by each field's `variableName`. See [Field object](#field-object).
</ParamField>

<ParamField body="outputs" type="object" required>
  The action's output schema, keyed by each field's `variableName`.
</ParamField>

<ParamField body="description" type="string">
  Action description. Maximum 2048 characters.
</ParamField>

<ParamField body="code" type="string">
  Python source that runs when the action executes. Evaluated in the global scope, not wrapped in a function: read caller values from `input_data[...]` and the connected secret from `credential_value`, then assign to `result[...]`. Maximum 65535 characters.
</ParamField>

## Field object

Each value inside `inputs` and `outputs`. The map key must equal the field's `variableName`.

<ParamField body="id" type="string" required>
  Positional identifier, 1-indexed as a string — `"1"`, `"2"`, and so on.
</ParamField>

<ParamField body="variableName" type="string" required>
  Programmatic key. Referenced from `code` as `input_data[<variableName>]` for inputs, or `result[<variableName>]` for outputs.
</ParamField>

<ParamField body="displayName" type="string" required>
  Human-readable label rendered in the workflow builder.
</ParamField>

<ParamField body="allowedTypes" type="object[]" required>
  Value types this field accepts. Between 1 and 11 entries. Each has a `type` — one of `int`, `float`, `bool`, `str`, `object`, `file`, `array`, `date`, `binary`, `json_string`, `node` — plus optional `typeDefinition` and `typeDefinitionEnforced`.
</ParamField>

<ParamField body="description" type="string">
  Description shown next to the field in the builder. Maximum 2048 characters.
</ParamField>

<ParamField body="default" type="any">
  Pre-filled default value. Its type must match one of `allowedTypes`.
</ParamField>

<ParamField body="isNullable" type="boolean" default="false">
  When true, the workflow may omit this field at execute time.
</ParamField>

<ParamField body="tags" type="string[]">
  Free-form tags the builder uses for grouping and filtering. Maximum 20.
</ParamField>

<ParamField body="options" type="object[]">
  Enum-style choice list. When non-empty the builder renders a dropdown. Maximum 20.
</ParamField>

<ParamField body="nonEditableFields" type="string[]">
  Top-level field names the builder locks. Maximum 20.
</ParamField>

<ParamField body="canDelete" type="boolean" default="true">
  When false, the builder hides the "remove field" affordance.
</ParamField>

<Note>
  For composite types, `typeDefinition` describes the shape inside: for `array`, a single-key map naming the element; for `object`, a map of its properties. Each value is itself a field definition and may nest further, so a list of records is `array` → `object` → properties. Maximum 50 entries per map.
</Note>

## Response

<ResponseField name="id" type="string" required>
  The integration id.
</ResponseField>

<ResponseField name="versionId" type="string" required>
  The integration version id. Use it as `{integrationVersionId}` in [Add a Credential](/api-reference/v1-integration/create-credential).
</ResponseField>

<ResponseField name="name" type="string" required>
  Integration name.
</ResponseField>

<ResponseField name="description" type="string" required>
  Integration description.
</ResponseField>

<ResponseField name="icon" type="string" required>
  Integration icon.
</ResponseField>

<ResponseField name="category" type="string[]" required>
  Integration categories.
</ResponseField>

<ResponseField name="requiredPlan" type="string" required>
  Minimum plan needed to connect it.
</ResponseField>

<ResponseField name="status" type="string" required>
  Integration status.
</ResponseField>

<ResponseField name="providerId" type="string" required>
  The provider the integration was authored under.
</ResponseField>

<ResponseField name="isNew" type="boolean" required>
  Whether the "new" badge is shown.
</ResponseField>

<ResponseField name="isPopular" type="boolean" required>
  Whether the "popular" badge is shown.
</ResponseField>

<ResponseField name="createdActions" type="object[]">
  Actions created inline, each `{ actionId, actionVersionId, name }`.
</ResponseField>

<ResponseField name="createdCredential" type="object">
  The credential created inline, as `{ credentialId, credentialVersionId, name }`. `null` when none was supplied.
</ResponseField>

## Errors

<ResponseField name="400" type="Bad Request">
  Invalid body — a missing required field, a value over its limit, more than 5 categories, or more than 50 actions.
</ResponseField>

<ResponseField name="401" type="Unauthorized">
  Missing, invalid, or expired API key.
</ResponseField>

<ResponseField name="403" type="Forbidden">
  Your organization is not verified to author integrations, or the key owner lacks the **Integration: Full** permission in the organization.
</ResponseField>

<ResponseField name="422" type="Unprocessable Entity">
  Rejected downstream after passing validation here.
</ResponseField>

<Warning>
  An inline action that fails does **not** roll back the integration — the integration row may already exist. Check before retrying, or you will create duplicates.
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://operator.opus.com/api/v1/integration \
    --header 'Content-Type: application/json' \
    --header 'x-service-key: {YOUR_SERVICE_KEY}' \
    --data '{
      "integration": {
        "name": "Acme Search",
        "description": "Search the web using the Acme API",
        "icon": "{BASE64_ICON}",
        "category": ["machine_learning_and_ai_services"],
        "providerId": "{PROVIDER_ID}",
        "actions": [
          {
            "name": "Search",
            "description": "Run a search query",
            "endpoint": "https://api.acme.com/v1/search",
            "code": "result[\"items\"] = search(input_data[\"query\"], credential_value)",
            "inputs": {
              "query": {
                "id": "1",
                "variableName": "query",
                "displayName": "Query",
                "allowedTypes": [{ "type": "str" }]
              }
            },
            "outputs": {
              "items": {
                "id": "1",
                "variableName": "items",
                "displayName": "Items",
                "allowedTypes": [{ "type": "array" }]
              }
            }
          }
        ]
      }
    }'
  ```

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

  url = "https://operator.opus.com/api/v1/integration"
  headers = {
      "Content-Type": "application/json",
      "x-service-key": "{YOUR_SERVICE_KEY}"
  }
  payload = {
      "integration": {
          "name": "Acme Search",
          "description": "Search the web using the Acme API",
          "icon": "{BASE64_ICON}",
          "category": ["machine_learning_and_ai_services"],
          "providerId": "{PROVIDER_ID}",
          "actions": [
              {
                  "name": "Search",
                  "endpoint": "https://api.acme.com/v1/search",
                  "code": 'result["items"] = search(input_data["query"], credential_value)',
                  "inputs": {
                      "query": {
                          "id": "1",
                          "variableName": "query",
                          "displayName": "Query",
                          "allowedTypes": [{"type": "str"}],
                      }
                  },
                  "outputs": {
                      "items": {
                          "id": "1",
                          "variableName": "items",
                          "displayName": "Items",
                          "allowedTypes": [{"type": "array"}],
                      }
                  },
              }
          ],
      }
  }

  response = requests.post(url, json=payload, headers=headers)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://operator.opus.com/api/v1/integration", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-service-key": "{YOUR_SERVICE_KEY}",
    },
    body: JSON.stringify({
      integration: {
        name: "Acme Search",
        description: "Search the web using the Acme API",
        icon: "{BASE64_ICON}",
        category: ["machine_learning_and_ai_services"],
        providerId: "{PROVIDER_ID}",
        actions: [
          {
            name: "Search",
            endpoint: "https://api.acme.com/v1/search",
            code: 'result["items"] = search(input_data["query"], credential_value)',
            inputs: {
              query: {
                id: "1",
                variableName: "query",
                displayName: "Query",
                allowedTypes: [{ type: "str" }],
              },
            },
            outputs: {
              items: {
                id: "1",
                variableName: "items",
                displayName: "Items",
                allowedTypes: [{ type: "array" }],
              },
            },
          },
        ],
      },
    }),
  });

  const data = await response.json();
  console.log(data);
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Response theme={null}
  {
    "id": "{INTEGRATION_ID}",
    "name": "Acme Search",
    "description": "Search the web using the Acme API",
    "icon": "{BASE64_ICON}",
    "category": ["machine_learning_and_ai_services"],
    "requiredPlan": "core",
    "status": "active",
    "providerId": "{PROVIDER_ID}",
    "isNew": true,
    "isPopular": false,
    "versionId": "{INTEGRATION_VERSION_ID}",
    "createdActions": [
      {
        "actionId": "{ACTION_ID}",
        "actionVersionId": "{ACTION_VERSION_ID}",
        "name": "Search"
      }
    ],
    "createdCredential": null
  }
  ```
</ResponseExample>
