---
title: "Set Auth Provider"
method: PUT
path: "/tenants/{id}/auth-provider"
tags: ["tenants"]
---

# Set Auth Provider

`PUT /tenants/{id}/auth-provider`

## Request

- Base URL: `https://api.instruxi.dev/api/v1/enforcer`
- URL: `https://api.instruxi.dev/api/v1/enforcer/tenants/{id}/auth-provider`
- Auth: one of:
  - API key in header `Authorization`
  - API key in header `X-API-Key`

## Path parameters

- `id` string, required

## Request body

- EnforcerV3InternalUsecaseTenantsAuthProviderInput
  - `privy_app_id` string: required when provider=privy
  - `provider` string: "native" | "privy" | "enterprise"

## Response `200`

OK

- InternalDeliveryHttpHandlerTenantResponse
  - `data` EnforcerV3InternalDomainTenantTenant
    - `account_abstraction` boolean
    - `allow_user_discovery` boolean: AllowUserDiscovery gates contact discovery for the whole tenant: when true, accounts may be discoverable (and new accounts default to discoverable); when false, discovery is off tenant-wide regardless of account.is_discoverable.
    - `app_url` string
    - `auth_provider` string: AuthProvider / WalletProvider: nil = inherit the instance default.
    - `banner_url` string
    - `billing_status` string: BillingStatus is the tenant's platform-billing dunning state, PROJECTED from enforcer-billing (migration 000087): "" | active | past_due | restricted | suspended | collections. enforcer-billing is the source of truth; this is a copy the billing.status_changed consumer keeps current so the value rides Accessor.billing_status on every resolve with no hop. "" means UNKNOWN and readers MUST treat it as active (fail open). `<-:false` makes GORM never write these two columns: Repository.Update is a whole-row Save, so without it an admin editing the tenant's name from a stale struct would silently roll the projection back. Only BillingStatusStore.ApplyBillingStatus writes them, by targeted UPDATE under the monotonic entered_at guard.
    - `billing_status_entered_at` string
    - `code` string
    - `code_expires_at` string: CodeExpiresAt is when the join code stops working everywhere a code is accepted, signing in and joining alike, as if it had been rotated away. Absent: the code never expires. Set by POST /tenants/{id}/code.
    - `created_at` string
    - `description` string
    - `id` string
    - `logo_url` string
    - `metadata` EnforcerV3InternalDomainTenantJSONBMap
    - `metered_sends_enabled` boolean: MeteredSendsEnabled turns on per-transaction limit metering: every value leg leaving the platform is measured against the caller's live cap before the server co-signs. Meaningful only while Web3Enabled is true — the pair (false, true) is refused on write; see ValidateWeb3Gates.
    - `modules` string[]: Modules is the tenant's MODULE entitlements as group slugs, prefixed `module_` (migration 000094, V3-02). Which HALO-Lego surfaces this tenant has bought — NOT which of them any given account may use, which is the role's question. It lives on the tenant rather than in account_groups because it is a fact about the TENANT: a module per account would mean auto-joining every new account and would make "remove one person from a module the tenant paid for" expressible. The resolver UNIONS this into Accessor.GroupSlugs, so every leaf's RequireModule reads it with no new field and no extra hop. Same `<-:false` guard and the same targeted-write rule as BillingStatus above: Repository.Update is a whole-row Save, so without it an admin editing the tenant's name from a stale struct would silently revoke every module. Only ModuleStore writes these two columns.
    - `modules_updated_at` string
    - `name` string
    - `payment_link_url` string
    - `privy_app_id` string
    - `self_join_policy` 'open' | 'invite_only'
    - `status` 'active' | 'inactive' | 'suspended'
    - `theme_colors` EnforcerV3InternalDomainTenantJSONBMap
    - `thumbnail_url` string
    - `updated_at` string
    - `wallet_provider` string
    - `web3_enabled` boolean: Web3Enabled is the tenant-wide web3 kill switch. False rejects EVERY web3 transaction for the tenant — guarded sends, signing, and RPC writes alike — so it is deliberately a gate rather than an enforcement mode. Defaults true (migration 000066) because false would disable web3 for every existing tenant on deploy.
  - `success` boolean

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `409`: Conflict

---

[API](https://skmtc.dev/instruxi/apis/enforcer-api.md) · [All operations](https://skmtc.dev/instruxi/apis/enforcer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/instruxi/apis/enforcer-api/revisions/fa82bc1becc0?raw)
