> ## 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.

# Add a Credential

> Define how an integration authenticates

Define how an integration authenticates: which fields exist, their types, and how they render in the Opus app.

This is the credential **schema** — it carries no secret values. Real secrets are supplied later, when someone connects the integration inside a workspace.

Use the `versionId` returned by [Create an Integration](/api-reference/v1-integration/create-integration) as `{integrationVersionId}`.

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

<Note>
  An integration may hold only one credential per `authType`. Adding a second with the same type returns `409`.
</Note>

## Headers

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

## Path Parameters

<ParamField path="integrationVersionId" type="string" required>
  The integration version to attach the credential to. This is `versionId` from [Create an Integration](/api-reference/v1-integration/create-integration) — not the integration's `id`.
</ParamField>

## Body Parameters

The credential is nested under a `credential` key.

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

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

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

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

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

<ParamField body="credential.authType" type="string" default="api_key">
  Authentication type — `jwt`, `oauth`, `api_key`, `access_token`, `custom_header_auth`, `other` or `no_auth`.
</ParamField>

<ParamField body="credential.status" type="string" default="active">
  Credential status — `active` or `inactive`.
</ParamField>

## Input field object

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

<ParamField body="displayName" type="string" required>
  Label rendered in the UI. Maximum 255 characters.
</ParamField>

<ParamField body="variableName" type="string" required>
  Programmatic key for this field. Must equal the key it sits under. Maximum 255 characters.
</ParamField>

<ParamField body="type" type="string" required>
  Value type accepted by this field — `str`, `int`, `float`, `bool`, `date`, `list`, `dict`, `set`, `object`, `array`, `file` or `array_files`.
</ParamField>

<ParamField body="order" type="number" default="1">
  UI rendering order.
</ParamField>

<ParamField body="placeholder" type="string">
  Placeholder text shown in the input box. Maximum 255 characters.
</ParamField>

<ParamField body="isSecret" type="boolean" default="true">
  When true, the value is masked in the UI and encrypted at rest.
</ParamField>

<ParamField body="isNullable" type="boolean" default="true">
  Whether the field may be left empty.
</ParamField>

<ParamField body="isEditable" type="boolean" default="true">
  When false, the field is read-only.
</ParamField>

<ParamField body="isVisible" type="boolean" default="true">
  When false, the field is hidden from the UI but stays part of the schema.
</ParamField>

<ParamField body="default" type="any">
  Pre-filled default value rendered in the UI.
</ParamField>

## Response

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

<ResponseField name="versionId" type="string" required>
  The credential version id.
</ResponseField>

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

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

<ResponseField name="inputs" type="object" required>
  The credential input schema, echoed back in the same shape you sent.
</ResponseField>

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

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

<ResponseField name="authType" type="string" required>
  Authentication type.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  Creation timestamp (ISO 8601).
</ResponseField>

## Errors

<ResponseField name="400" type="Bad Request">
  Invalid body — a missing required field, or a value over its limit.
</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, the key owner lacks the **Integration: Full** permission, or the integration belongs to another organization.
</ResponseField>

<ResponseField name="404" type="Not Found">
  The integration version does not exist. Check you are using `versionId` and not the integration's `id`.
</ResponseField>

<ResponseField name="409" type="Conflict">
  This integration already has a credential with the same `authType`.
</ResponseField>

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

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://operator.opus.com/api/v1/integration/v/{INTEGRATION_VERSION_ID}/credential \
    --header 'Content-Type: application/json' \
    --header 'x-service-key: {YOUR_SERVICE_KEY}' \
    --data '{
      "credential": {
        "name": "Acme API Key",
        "description": "API key for the Acme Search API",
        "icon": "{BASE64_ICON}",
        "authType": "api_key",
        "inputs": {
          "api_key": {
            "displayName": "API Key",
            "variableName": "api_key",
            "type": "str",
            "order": 1,
            "placeholder": "Enter your Acme API key",
            "isSecret": true
          }
        }
      }
    }'
  ```

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

  version_id = "{INTEGRATION_VERSION_ID}"
  url = f"https://operator.opus.com/api/v1/integration/v/{version_id}/credential"
  headers = {
      "Content-Type": "application/json",
      "x-service-key": "{YOUR_SERVICE_KEY}"
  }
  payload = {
      "credential": {
          "name": "Acme API Key",
          "description": "API key for the Acme Search API",
          "icon": "{BASE64_ICON}",
          "authType": "api_key",
          "inputs": {
              "api_key": {
                  "displayName": "API Key",
                  "variableName": "api_key",
                  "type": "str",
                  "order": 1,
                  "placeholder": "Enter your Acme API key",
                  "isSecret": True,
              }
          },
      }
  }

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

  ```javascript JavaScript theme={null}
  const versionId = "{INTEGRATION_VERSION_ID}";

  const response = await fetch(
    `https://operator.opus.com/api/v1/integration/v/${versionId}/credential`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-service-key": "{YOUR_SERVICE_KEY}",
      },
      body: JSON.stringify({
        credential: {
          name: "Acme API Key",
          description: "API key for the Acme Search API",
          icon: "{BASE64_ICON}",
          authType: "api_key",
          inputs: {
            api_key: {
              displayName: "API Key",
              variableName: "api_key",
              type: "str",
              order: 1,
              placeholder: "Enter your Acme API key",
              isSecret: true,
            },
          },
        },
      }),
    }
  );

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

<ResponseExample>
  ```json 201 Response theme={null}
  {
    "id": "{CREDENTIAL_ID}",
    "name": "Acme API Key",
    "description": "API key for the Acme Search API",
    "inputs": {
      "api_key": {
        "displayName": "API Key",
        "variableName": "api_key",
        "type": "str",
        "order": 1,
        "isNullable": false,
        "placeholder": "Enter your Acme API key",
        "isSecret": true,
        "isEditable": true,
        "isVisible": true,
        "default": null
      }
    },
    "status": "active",
    "icon": "{BASE64_ICON}",
    "authType": "api_key",
    "versionId": "{CREDENTIAL_VERSION_ID}",
    "createdAt": "2026-08-13T10:31:02.000Z"
  }
  ```
</ResponseExample>
