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

# Quick start

> Two API calls. Verified unique humans in your product.

We handle bots, dupes, and fraud before they get to you.
All you decide is where to put the human check in your flow and what happens when someone passes.

<Note>
  These endpoints are public and heavily rate limited. Grab an
  [API key](/v2/authentication) to remove the limits.
</Note>

<Steps>
  <Step title="Create a verification">
    ## Step 1: Create a verification

    Call `POST /v2/verification/create` from your backend with a return URL. You get back a link; send your user there.

    <CodeGroup>
      ```javascript Node.js theme={null}
      const response = await fetch(
        "https://api.connect.verifyyou.com/v2/verification/create",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            redirect: "https://yourapp.com/verified",
          }),
        }
      );

      const data = await response.json();
      const verificationUrl = data.verification_url;
      // "https://verifyyou.com/invite?session_id=zsjmq6"
      ```

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

      response = requests.post(
          "https://api.connect.verifyyou.com/v2/verification/create",
          json={
              "redirect": "https://yourapp.com/verified",
          },
      )

      data = response.json()
      verification_url = data["verification_url"]
      # "https://verifyyou.com/invite?session_id=zsjmq6"
      ```

      ```typescript TypeScript theme={null}
      interface CreateVerificationResponse {
        verification_url: string;
        external_id: string;
      }

      const response = await fetch(
        "https://api.connect.verifyyou.com/v2/verification/create",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            redirect: "https://yourapp.com/verified",
          }),
        }
      );

      const data: CreateVerificationResponse = await response.json();
      const verificationUrl = data.verification_url;
      // "https://verifyyou.com/invite?session_id=zsjmq6"
      ```

      ```csharp C# theme={null}
      using var client = new HttpClient();

      var response = await client.PostAsync(
          "https://api.connect.verifyyou.com/v2/verification/create",
          new StringContent(
              """{"redirect": "https://yourapp.com/verified"}""",
              System.Text.Encoding.UTF8,
              "application/json"
          )
      );

      var body = await response.Content.ReadAsStringAsync();
      // verification_url: "https://verifyyou.com/invite?session_id=zsjmq6"
      ```

      ```java Java theme={null}
      var request = HttpRequest.newBuilder()
          .uri(URI.create("https://api.connect.verifyyou.com/v2/verification/create"))
          .header("Content-Type", "application/json")
          .POST(HttpRequest.BodyPublishers.ofString("""
              {"redirect": "https://yourapp.com/verified"}
              """))
          .build();

      var response = client.send(request, HttpResponse.BodyHandlers.ofString());
      // verification_url: "https://verifyyou.com/invite?session_id=zsjmq6"
      ```

      ```php PHP theme={null}
      $response = file_get_contents("https://api.connect.verifyyou.com/v2/verification/create", false,
          stream_context_create(["http" => [
              "method" => "POST",
              "header" => "Content-Type: application/json",
              "content" => json_encode(["redirect" => "https://yourapp.com/verified"]),
          ]])
      );

      $data = json_decode($response, true);
      $verificationUrl = $data["verification_url"];
      // "https://verifyyou.com/invite?session_id=zsjmq6"
      ```

      ```go Go theme={null}
      body, _ := json.Marshal(map[string]string{
          "redirect": "https://yourapp.com/verified",
      })

      req, _ := http.NewRequest("POST",
          "https://api.connect.verifyyou.com/v2/verification/create",
          bytes.NewBuffer(body),
      )
      req.Header.Set("Content-Type", "application/json")

      resp, _ := http.DefaultClient.Do(req)
      defer resp.Body.Close()
      // verification_url: "https://verifyyou.com/invite?session_id=zsjmq6"
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "verification_url": "https://verifyyou.com/invite?session_id=zsjmq6",
      "external_id": "d4f8e2a1-..."
    }
    ```

    | Parameter  | Description                                                                                                                                                         |
    | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `redirect` | Where to return the user after verification. `vy_token` and `vy_status` query parameters are appended.                                                              |
    | `phone`    | Optional. E.164 phone number. When supplied, `verification_url` skips the phone-OTP step. See [Skip phone verification](#skip-phone-verification-with-phone) below. |

    <Info>
      You can add `external_id`s to help keep track of accounts. See [External IDs](/v2/external-ids) for details.
    </Info>

    <Info>
      Redirect the user to `verification_url`: open it as a redirect, in a modal webview, or a new tab.
    </Info>

    <Accordion title="Skip phone verification with `phone`" icon="phone">
      ### Skip phone verification with `phone`

      If you've already verified the user's phone number on your end (e.g. your own SMS login, an authenticated session), pass it in `phone`. The returned `verification_url` will land the user directly on the liveness check, with no phone-OTP step. Same field, same code path on your side; only the URL shape changes.

      ```json Request theme={null}
      {
        "redirect": "https://yourapp.com/verified",
        "phone": "+12105550142"
      }
      ```

      ```json Response theme={null}
      {
        "verification_url": "https://verifyyou.com/verify?session_id=zsjmq6&t=eyJwaG9uZU51bWJlciI6IisxMjEwNTU1MDE0MiIsInBob25lQ29kZSI6IjU1MDE0MiJ9",
        "external_id": "d4f8e2a1-..."
      }
      ```

      <Warning>
        The `verification_url` is the trust anchor; anyone with it can complete the flow as that user. Deliver it over a channel you've already authenticated (post-login redirect, authenticated email, push notification). Don't drop it in a public page or unauthenticated email.
      </Warning>

      `phone` skips the OTP step, not the verification itself; every call runs the full liveness check.
    </Accordion>
  </Step>

  <Step title="We verify them">
    ## Step 2: We verify them

    Once the user lands on our flow, VerifyYou runs a two-stage check automatically:

    1. **Phone number check**: a verified phone number acts as a persistent identifier that follows the user across devices, browsers, and sessions.
    2. **Liveness + uniqueness scan**: confirms a real human is present and they haven't verified under a different account.

    <Info>
      **Desktop vs. mobile.** If your user hits the flow on desktop, we hand them off to their phone via a QR code: they scan, complete the liveness capture on mobile, and (on success) the desktop session automatically advances and redirects back to your `redirect` URL. Users who start on mobile stay on mobile and complete the whole flow on one device. Either way, your app just gets one redirect back with the same query params; you don't need to branch your integration on device type.
    </Info>

    <Accordion title="Why a verification can be denied">
      A `denied` result isn't a single thing; it means the user failed one of a few checks:

      * **Liveness**: the capture didn't confirm a real person was present (e.g. photo of a photo, screen replay, mask).
      * **Uniqueness**: the person has already verified under another account, either in your app or elsewhere on our network.
      * **Risk signals**: patterns consistent with fraudulent attempts.

      We don't return the specific reason per attempt on purpose. Exposing which check failed would give bad actors a map of what to try next. That said, we're working on getting this data back to you in a shape that's actually useful; more on that down the road.
    </Accordion>

    When done, we redirect back to your `redirect` URL with query params appended:

    ```
    https://yourapp.com/verified?vy_token=vt:a1b2c3d4-e5f6-7890-abcd-ef1234567890&vy_status=approved
    ```

    | Param       | Values                                                                                                |
    | ----------- | ----------------------------------------------------------------------------------------------------- |
    | `vy_status` | `approved`: passed all checks. `denied`: did not pass.                                                |
    | `vy_token`  | Short-lived verification token (expires after 15 minutes). Use this in step 3 to confirm server-side. |

    <Note>
      Want to try the flow right now? Generate a test phone number with the [sandbox](/v2/testing); no API key needed:

      ```bash theme={null}
      curl -s https://api.connect.verifyyou.com/v2/sandbox/phone-number -H "Content-Type: application/json" -d '{}'
      ```
    </Note>

    <Note>
      Running the flow yourself? There are a few quirks worth knowing: test phone numbers, account stickiness across browsers, and human review. See [Testing the flow](/v2/testing#what-to-expect-when-testing) before you start.
    </Note>

    <Accordion title="Extract query params from the redirect">
      <CodeGroup>
        ```javascript Node.js / Express theme={null}
        app.get("/verified", (req, res) => {
          const vyToken = req.query.vy_token;
          const vyStatus = req.query.vy_status;
        });
        ```

        ```typescript TypeScript theme={null}
        const params = new URLSearchParams(window.location.search);
        const vyToken = params.get("vy_token");
        const vyStatus = params.get("vy_status");
        ```

        ```php PHP theme={null}
        $vyToken = $_GET["vy_token"];
        $vyStatus = $_GET["vy_status"];
        ```
      </CodeGroup>
    </Accordion>
  </Step>

  <Step title="Confirm the result">
    ## Step 3: Confirm the result

    Never trust the `vy_status` query param alone; anyone can fake a URL. Call `POST /v2/verification/status` from your backend to confirm.

    <Info>
      Safe to poll: an in-flight verification (created but the user hasn't finished yet) returns `200` with `verification_complete: false`. A `404` only happens for tokens or `external_id`s that don't match any verification.
    </Info>

    **Option A, check by token:**

    <CodeGroup>
      ```javascript Node.js theme={null}
      const response = await fetch(
        "https://api.connect.verifyyou.com/v2/verification/status",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ token: vyToken }),
        }
      );

      const data = await response.json();
      if (data.verification_complete) {
        console.log("User is verified!");
      }
      ```

      ```python Python theme={null}
      response = requests.post(
          "https://api.connect.verifyyou.com/v2/verification/status",
          json={"token": vy_token},
      )

      data = response.json()
      if data["verification_complete"]:
          print("User is verified!")
      ```

      ```typescript TypeScript theme={null}
      interface VerificationStatusResponse {
        verification_complete: boolean;
        external_id: string;
      }

      const response = await fetch(
        "https://api.connect.verifyyou.com/v2/verification/status",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ token: vyToken }),
        }
      );

      const data: VerificationStatusResponse = await response.json();
      if (data.verification_complete) {
        console.log("User is verified!");
      }
      ```

      ```csharp C# theme={null}
      var response = await client.PostAsync(
          "https://api.connect.verifyyou.com/v2/verification/status",
          new StringContent(
              $"{{\"token\": \"{vyToken}\"}}",
              System.Text.Encoding.UTF8,
              "application/json"
          )
      );

      var body = await response.Content.ReadAsStringAsync();
      ```

      ```java Java theme={null}
      var request = HttpRequest.newBuilder()
          .uri(URI.create("https://api.connect.verifyyou.com/v2/verification/status"))
          .header("Content-Type", "application/json")
          .POST(HttpRequest.BodyPublishers.ofString(
              "{\"token\": \"" + vyToken + "\"}"
          ))
          .build();

      var response = client.send(request, HttpResponse.BodyHandlers.ofString());
      ```

      ```php PHP theme={null}
      $response = file_get_contents("https://api.connect.verifyyou.com/v2/verification/status", false,
          stream_context_create(["http" => [
              "method" => "POST",
              "header" => "Content-Type: application/json",
              "content" => json_encode(["token" => $vyToken]),
          ]])
      );

      $data = json_decode($response, true);
      if ($data["verification_complete"]) {
          echo "User is verified!";
      }
      ```

      ```go Go theme={null}
      body, _ := json.Marshal(map[string]string{"token": vyToken})

      req, _ := http.NewRequest("POST",
          "https://api.connect.verifyyou.com/v2/verification/status",
          bytes.NewBuffer(body),
      )
      req.Header.Set("Content-Type", "application/json")

      resp, _ := http.DefaultClient.Do(req)
      defer resp.Body.Close()
      ```
    </CodeGroup>

    **Option B, check by external ID:**

    Use this when the token has expired (after 15 minutes), or when you want to check status without a redirect flow.

    <CodeGroup>
      ```javascript Node.js theme={null}
      const response = await fetch(
        "https://api.connect.verifyyou.com/v2/verification/status",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ external_id: "user_8f3a29c1" }),
        }
      );

      const data = await response.json();
      ```

      ```python Python theme={null}
      response = requests.post(
          "https://api.connect.verifyyou.com/v2/verification/status",
          json={"external_id": "user_8f3a29c1"},
      )

      data = response.json()
      if data["verification_complete"]:
          print("User is verified!")
      ```

      ```typescript TypeScript theme={null}
      const response = await fetch(
        "https://api.connect.verifyyou.com/v2/verification/status",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ external_id: "user_8f3a29c1" }),
        }
      );

      const data: VerificationStatusResponse = await response.json();
      ```

      ```csharp C# theme={null}
      var response = await client.PostAsync(
          "https://api.connect.verifyyou.com/v2/verification/status",
          new StringContent(
              """{"external_id": "user_8f3a29c1"}""",
              System.Text.Encoding.UTF8,
              "application/json"
          )
      );

      var body = await response.Content.ReadAsStringAsync();
      ```

      ```java Java theme={null}
      var request = HttpRequest.newBuilder()
          .uri(URI.create("https://api.connect.verifyyou.com/v2/verification/status"))
          .header("Content-Type", "application/json")
          .POST(HttpRequest.BodyPublishers.ofString(
              "{\"external_id\": \"user_8f3a29c1\"}"
          ))
          .build();

      var response = client.send(request, HttpResponse.BodyHandlers.ofString());
      ```

      ```php PHP theme={null}
      $response = file_get_contents("https://api.connect.verifyyou.com/v2/verification/status", false,
          stream_context_create(["http" => [
              "method" => "POST",
              "header" => "Content-Type: application/json",
              "content" => json_encode(["external_id" => "user_8f3a29c1"]),
          ]])
      );

      $data = json_decode($response, true);
      if ($data["verification_complete"]) {
          echo "User is verified!";
      }
      ```

      ```go Go theme={null}
      body, _ := json.Marshal(map[string]string{
          "external_id": "user_8f3a29c1",
      })

      req, _ := http.NewRequest("POST",
          "https://api.connect.verifyyou.com/v2/verification/status",
          bytes.NewBuffer(body),
      )
      req.Header.Set("Content-Type", "application/json")

      resp, _ := http.DefaultClient.Do(req)
      defer resp.Body.Close()
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "verification_complete": true,
      "external_id": "user_8f3a29c1"
    }
    ```

    <Info>
      **Status answers at the user level, not the session level.** Looking up by `external_id` returns `true` as soon as *any* session for that user has completed. Useful if you call `verification/create` more than once for the same user (e.g. to deliver the URL via different channels). See [Reusing the same external\_id](/v2/external-ids#reusing-the-same-external-id) for details. Looking up by `token` is always session-scoped.
    </Info>

    <Info>
      That's it. Two endpoints, one redirect, one check. Your user is verified as a real, unique human.
    </Info>
  </Step>
</Steps>

<Card title="API Reference" icon="square-terminal" href="/v2/openapi.yaml">
  Full endpoint docs with interactive playground
</Card>
