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

# Mail tenant credentials

> Admin-only callback endpoint for the mail system to register or rotate tenant credentials for a Voice team

`mail-tenant-credentials` is an admin-only resource that lets the mail system push the credentials it issued (or reissued) for a Voice team back into HelpGenie Voice. It is the inbound counterpart to the outbound tenant-provisioning call made when a genie mailbox is first created.

If the `owner_email` address has no Voice account yet, one is created automatically. If that account has no team, a team is created as well — so the mail system always receives a stable `team_id` to store on its side.

<Warning>
  All actions on this resource require an internal admin API key. Requests from non-admin callers are rejected with `403 Forbidden`.
</Warning>

***

## Upsert tenant credentials

Registers or updates the mail tenant credentials for the team that owns `owner_email`. Creates a Voice account and team for `owner_email` if neither exists yet.

<ParamField body="resource" type="string" required>
  Must be `"mail-tenant-credentials"`
</ParamField>

<ParamField body="action" type="string" required>
  Must be `"upsert"`
</ParamField>

<ParamField body="data" type="object" required>
  <Expandable title="properties">
    <ParamField body="owner_email" type="string" required>
      Email address of the Voice team owner. Must be a valid email address. Used to look up (or create) the Voice account that owns the tenant.
    </ParamField>

    <ParamField body="tenant_id" type="string" required>
      The tenant identifier issued by the mail system for this team.
    </ParamField>

    <ParamField body="api_key" type="string" required>
      The API key issued by the mail system for this tenant.
    </ParamField>

    <ParamField body="tenant_name" type="string | null">
      Display name for the tenant. Pass `null` to clear an existing value.
    </ParamField>

    <ParamField body="owner_user_id" type="string | null">
      The mail system's own user identifier for this owner. Pass `null` if not available.
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="success" type="boolean">
  Whether the credentials were stored successfully.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="team_id" type="number">
      The Voice team ID the credentials were stored under. The mail system should persist this to associate future updates with the same team.
    </ResponseField>

    <ResponseField name="owner_user_id" type="string">
      The Voice user ID for `owner_email`. Matches the ID of the newly created account if one was provisioned during this call.
    </ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```typescript ApiService.invoke() theme={null}
  const response = await ApiService.invoke<{
    team_id: number;
    owner_user_id: string;
  }>({
    resource: "mail-tenant-credentials",
    action: "upsert",
    data: {
      owner_email: "owner@example.com",
      tenant_id: "tenant-abc123",
      api_key: "mk_live_xxxxxxxxxxxxxxxx",
      tenant_name: "Example Team",
      owner_user_id: "mail-usr-456",
    },
  });

  const { team_id, owner_user_id } = response;
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.helpgenie.ai/v1 \
    -H "Authorization: Bearer hg_live_YOUR_ADMIN_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "resource": "mail-tenant-credentials",
      "action": "upsert",
      "data": {
        "owner_email": "owner@example.com",
        "tenant_id": "tenant-abc123",
        "api_key": "mk_live_xxxxxxxxxxxxxxxx",
        "tenant_name": "Example Team",
        "owner_user_id": "mail-usr-456"
      }
    }'
  ```

  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "team_id": 42,
      "owner_user_id": "550e8400-e29b-41d4-a716-446655440000"
    }
  }
  ```
</CodeGroup>

***

## Side effects

* If `owner_email` does not match any existing Voice account, a new account is created with a temporary password. The owner will need to complete account setup before logging in directly.
* If the resolved account has no team, a team is created automatically using the local part of `owner_email` as the team name.
* Calling `upsert` again for the same `owner_email` with new credentials will overwrite the previously stored values — no duplicate entries are created.

***

## Error codes

| Code               | Status | Description                                                                          |
| ------------------ | ------ | ------------------------------------------------------------------------------------ |
| `FORBIDDEN`        | 403    | Caller is not an internal admin                                                      |
| `VALIDATION_ERROR` | 400    | `owner_email` is missing or invalid, `tenant_id` is missing, or `api_key` is missing |
| `INVALID_ACTION`   | 400    | Unknown action                                                                       |
| `INTERNAL_ERROR`   | 500    | Account or team creation failed, or credential storage failed                        |
