---
title: "Create verification provider"
method: POST
path: "/admin/verification-providers"
tags: ["verification-providers"]
---

# Create verification provider

`POST /admin/verification-providers`

## Request

- Base URL: `https://api.instruxi.dev/api/v1/enforcer`
- URL: `https://api.instruxi.dev/api/v1/enforcer/admin/verification-providers`
- Auth: one of:
  - API key in header `Authorization`
  - API key in header `X-API-Key`

Provisions one verification provider for one tenant (one live row per tenant+name; a second is 409). Platform-admin only. Validated before write, all 400 invalid_provider_config: name must be a registered provider (activecampaign, cybrid, maxmd, stripe); api_endpoint is required; every action_group_map key must be in allowed_actions and every allowed action must route to a group (its map entry or target_group_name); target_group_name and every mapped group must be a live group SLUG in that tenant — the same lookup the grant uses, so a row that validates is a row that grants. auth_key is write-only: sealed at rest with the connection key ring, never returned, never audited. Unknown body keys are 400.

## Request body

- InternalDeliveryHttpHandlerCreateVerificationProviderRequest
  - `action_group_map` object: action -> group SLUG. Every key must be in allowed_actions; every value a live group in the tenant.
  - `allowed_actions` string[]: Actions this provider may serve. Every entry must route to a group (its action_group_map entry or target_group_name). Empty permits every action.
  - `api_endpoint` string: gRPC SessionProvider address, e.g. enforcer-cybrid:50054. Required on create.
  - `auth_key` string: WRITE-ONLY shared secret sent to the SessionProvider (x-verifier-key). Sealed at rest; never returned. "" clears it.
  - `idempotency_enabled` boolean: Reuse a pending session instead of minting a new one.
  - `insecure` boolean: Dial plaintext gRPC (cluster-internal); TLS otherwise.
  - `is_async` boolean: Provider completes out-of-band via the verification.callback event.
  - `name` string: Registered provider name: activecampaign, cybrid, maxmd or stripe.
  - `sensitive_fields` string[]: Response fields returned to the client but never persisted.
  - `session_ttl` integer: Session lifetime in seconds.
  - `target_group_name` string: Default group SLUG granted on a pass. Must be a live group in the tenant.
  - `tenant_id` string: Target tenant. Required: the platform admin names the tenant explicitly.

## Response `201`

Created

- InternalDeliveryHttpHandlerVerificationProviderResponse
  - `data` EnforcerV3InternalUsecaseVerificationprovidersView
    - `action_group_map` object
    - `allowed_actions` string[]
    - `api_endpoint` string
    - `auth_key_set` boolean: AuthKeySet reports whether the row has an auth key at all.
    - `auth_key_storage` 'encrypted' | 'plaintext' | 'none': AuthKeyStorage is "encrypted" (sealed column), "plaintext" (a legacy hand-inserted row's metadata.auth_key — re-set it through PUT to seal it) or "none".
    - `created_at` string
    - `id` string
    - `idempotency_enabled` boolean
    - `insecure` boolean
    - `is_async` boolean
    - `name` string
    - `sensitive_fields` string[]
    - `session_ttl` integer
    - `target_group_name` string
    - `tenant_id` string
    - `updated_at` string
  - `success` boolean

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `409`: Conflict
- `500`: Internal Server Error
- `503`: Service Unavailable

---

[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)
