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

# Verify by External Provider

> Submit a decisioned KYC result from your own identity verification provider instead of having Fluz run the verification.

If your platform already runs KYC with its own identity provider, your customers should not have to verify twice. Submit the decisioned result to Fluz with `postKycVerification`.

Unlike the other API methods, Fluz does not screen the identity itself here — the decision is yours. Fluz only validates and records it, and moves the customer's status accordingly.

<Info>
  **Prerequisites**

  * An external KYC program approved by Fluz for your application. Talk to your account manager before building against this method.
  * The `VERIFY_KYC` scope enabled on your application by Fluz. See [Required scope](/user-kyc-verification#required-scope).
  * A [user access token](/recipes/generate-user-access-token) generated for the customer being verified, including `VERIFY_KYC` in its scopes.
  * The dedicated secure ingestion endpoint for your environment, provided during onboarding.
</Info>

<Warning>
  Send this mutation to the **dedicated secure ingestion endpoint**, not the standard API host. This endpoint securely tokenizes PII data like `person.ssn` in transit. A request whose SSN or other PII data arrive untokenized is rejected. Always pass the arguments as GraphQL **variables**; values inlined into the query document cannot be tokenized.

  * Staging: `https://secure.transactional-graph.staging.fluzapp.com/api/v1/graphql`
  * Production: provided during onboarding
</Warning>

## How it works

The `postKycVerification` mutation is synchronous. You submit the verified identity and the provider verifications backing your decision, and Fluz returns `APPROVED`, `DECLINED`, `DUPLICATE`, or `ERROR` in the response body. There is no customer-facing step.

A few behaviors are specific to this method:

* **Only decisioned results.** `decision` must be `PASSED` or `FAILED`. Do not send pending or undecisioned verifications.
* **Idempotency.** Submissions are idempotent on `externalVerificationId` — your provider's unique id for the attempt. Replaying an id returns the original result and writes nothing; a re-decisioned verification must be submitted with a new id.
* **Status transitions.** A `PASSED` result moves an unverified customer to verified. If the customer is already verified — by any method — the verification is still recorded for audit, but their status is never changed; the response message notes that the existing status was preserved. A `FAILED` result is recorded and leaves the status untouched.
* **No attempt limit.** Because you are reporting results rather than requesting screening, this method has no attempt limit and does not count against the limits on [Verify by SSN](/verify-customers-by-ssn) or [KYC Autofill](/verify-customers-by-autofill).

## Request

The customer is identified by the user access token in the `Authorization` header — `person.userId` and your application identity are filled in by Fluz, and any submitted values are overwritten.

| Field                          | Type   | Required | Description                                                                                         |
| :----------------------------- | :----- | :------- | :-------------------------------------------------------------------------------------------------- |
| `schemaVersion`                | String | Yes      | Version of the payload contract. Currently `"1.0"`.                                                 |
| `externalVerificationProvider` | String | Yes      | `IDOLOGY`, `OSCILAR`, `PERSONA`, or `CUSTOM`.                                                       |
| `externalVerificationId`       | String | Yes      | Your provider's globally unique id for this attempt — the idempotency key.                          |
| `decision`                     | String | Yes      | `PASSED` or `FAILED`.                                                                               |
| `decisionReason`               | String | No       | Human-readable reason or rule that produced the decision.                                           |
| `decisionedAt`                 | String | No       | ISO 8601 timestamp of the decision. Defaults to the time of ingestion.                              |
| `person`                       | JSON   | Yes      | The verified identity as established by the provider.                                               |
| `verifications`                | JSON   | Yes      | The provider verifications backing the decision — at least one of `document`, `ssn`, or `database`. |
| `externalProviderData`         | JSON   | No       | Freeform provider context for audit.                                                                |

See [postKycVerification](/api-reference/mutations/post-kyc-verification) for the full field reference, including the `person` and `verifications` object shapes.

## Example

```javascript theme={null}
import { GraphQLClient, gql } from 'graphql-request';

const API_URL =
  'https://secure.transactional-graph.staging.fluzapp.com/api/v1/graphql';

const POST_KYC_VERIFICATION = gql`
  mutation postKycVerification(
    $schemaVersion: String!
    $externalVerificationProvider: String!
    $externalVerificationId: String!
    $decision: String!
    $decisionReason: String
    $person: JSON!
    $verifications: JSON!
  ) {
    postKycVerification(
      schemaVersion: $schemaVersion
      externalVerificationProvider: $externalVerificationProvider
      externalVerificationId: $externalVerificationId
      decision: $decision
      decisionReason: $decisionReason
      person: $person
      verifications: $verifications
    ) {
      status
      verificationId
      message
    }
  }
`;

const client = new GraphQLClient(API_URL, {
  headers: {
    Authorization: `Bearer <<USER_ACCESS_TOKEN>>`,
    'Content-Type': 'application/json',
  },
});

const response = await client.request(POST_KYC_VERIFICATION, {
  schemaVersion: '1.0',
  externalVerificationProvider: 'PERSONA',
  externalVerificationId: 'inq_gCf28LrXY9wrxDoZbnbEqTrn',
  decision: 'PASSED',
  decisionReason: 'All checks passed',
  person: {
    firstName: 'JANE Q',
    lastName: 'SAMPLE',
    dateOfBirth: '1990-01-01',
    ssn: '900-98-7654',
    ssnLast4: '7654',
    address: {
      streetLine1: '123 EXAMPLE STREET',
      city: 'SAMPLETOWN',
      subdivision: 'CA',
      postalCode: '90001',
      countryCode: 'US',
    },
  },
  verifications: {
    document: {
      verificationId: 'ver_p8qmKydzkQwyLKbaAJRvADi9',
      status: 'PASSED',
      documentClass: 'DRIVER_LICENSE',
      documentNumber: 'X90000000000001',
      issuingCountryCode: 'US',
      photos: { front: 'https://files.provider.example/front.jpg' },
      checks: [{ name: 'id_expired_detection', status: 'PASSED' }],
    },
    ssn: {
      verificationId: 'ver_tin_snW2CvL2x4kTdZjbmGBjqcuV',
      status: 'PASSED',
      source: 'TIN_DATABASE',
    },
  },
});

console.log(response);
```

```json Response theme={null}
{
  "data": {
    "postKycVerification": {
      "status": "APPROVED",
      "verificationId": "4b99e8c5-4201-45fd-a5dc-1c3b88e4f6c7",
      "message": "User verification successful"
    }
  }
}
```

## Handling the response

| Status      | What to do                                                                                                                                                                                                                   |
| :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APPROVED`  | The verification was recorded. If the customer was not yet verified, they now are; if they were already verified, `message` notes that the existing status was preserved.                                                    |
| `DECLINED`  | The `FAILED` result was recorded. The customer's status is unchanged.                                                                                                                                                        |
| `DUPLICATE` | The verification was recorded, but the SSN or document number matches another Fluz customer. The account is placed under review — investigate on your side before proceeding. Fluz does not disclose which customer matched. |
| `ERROR`     | Inspect `message` — most commonly a payload that failed contract validation. Nothing was recorded.                                                                                                                           |

## Testing

Test against the staging endpoint with fabricated identities — because the decision is yours, there are no provider test-identity requirements as with the other methods. Use SSNs in the `900-XX-XXXX` range (never issued), generate a fresh `externalVerificationId` per attempt (replaying an id returns the original result rather than exercising a new one), and make sure any photo URLs you send are fetchable — Fluz retrieves and stores the images.
