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

# Organization KYC/KYB/IDV onboarding

> Move an organization from signed-up to approved for live traffic: submit the KYC form, add optional hosted ID verification, poll the status endpoint, handle rejection, and finish the go-live gates.

# Organization KYC/KYB/IDV onboarding

The [Quickstart](/quickstart#step-5-go-live) Step 5 names two hard gates on live traffic: approved organization KYC and a funded balance. Organization KYC is a once-per-workspace review — it verifies *the business itself*, not its numbers (per-number document bundles are a separate concern; see the comparison at the end).

This guide walks the full loop — submit, optionally add a hosted identity-verification session, poll for the verdict, resubmit on rejection — then picks up the remaining go-live gates.

***

## 1. Why live traffic is gated

Live sends are restricted until someone on the Devotel operations team has looked at a real business profile. That friction exists because automated signup farms were draining trial credit into disposable accounts; a human review is the one control that breaks that loop. Until approval lands:

* Buying numbers is still possible, but no SMS, WhatsApp, or voice message will leave the platform.
* The dashboard surfaces the status as a banner; the same page this guide covers is reachable from **Settings → KYC**.

Two statuses carry the review forward. `not_started` means the form has never been submitted. `pending_review` means an operators queue has the submission (the email-confirmation webhook pre-stamps this on signup; the form below replaces it with rich detail). After a decision you get `approved` or `rejected`.

## 2. Submit the form

POST to `/api/v1/organization/kyc/submit` with the business profile. Field by field the API validates: company name and country are required, the website is optional, the use-case description must be ten characters or more, and beneficial-owner entries feed the sanctioned-party screening step (optional, max 20).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/organization/kyc/submit \
    -H "X-API-Key: $ORBIT_TEST_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "company_name": "Acme Logistics Ltd.",
      "company_website": "https://acme-logistics.example",
      "country": "US",
      "industry": "Logistics",
      "use_case": "Delivery-status notifications sent to customers who opted in at checkout.",
      "estimated_monthly_volume": 45000,
      "registration_number": "DE-554433",
      "beneficial_owners": [
        { "name": "Maria Alvarez", "ownership_percentage": 100 }
      ]
    }'
  ```

  ```typescript Node.js theme={null}
  import { Devotel } from '@devotel-orbit/node';

  const orbit = new Devotel({ apiKey: process.env.ORBIT_API_KEY });

  const result = await orbit.organization.kyc.submit({
    company_name: 'Acme Logistics Ltd.',
    company_website: 'https://acme-logistics.example',
    country: 'US',
    industry: 'Logistics',
    use_case: 'Delivery-status notifications sent to customers who opted in at checkout.',
    estimated_monthly_volume: 45000,
  });
  console.log(result.data.status); // "pending_review"
  ```
</CodeGroup>

A successful call stamps the organization `pending_review` and returns the stored record plus the business-screening verdict. Review is operator-run — nothing auto-approves or auto-rejects.

**Response (200):**

```json theme={null}
{
  "data": {
    "status": "pending_review",
    "kyc": {
      "status": "pending_review",
      "company_name": "Acme Logistics Ltd.",
      "company_website": "https://acme-logistics.example",
      "country": "US",
      "industry": "Logistics",
      "use_case": "Delivery-status notifications sent to customers who opted in at checkout.",
      "estimated_monthly_volume": 45000,
      "submitted_at": "2026-09-04T09:12:33Z"
    },
    "kyb": {
      "status": "review",
      "matches": [],
      "legal_name": "Acme Logistics Ltd.",
      "country": "US",
      "registration_number": "DE-554433",
      "beneficial_owners": [
        { "name": "Maria Alvarez", "ownership_percentage": 100, "country": null }
      ],
      "screened_at": "2026-09-04T09:12:33Z"
    },
    "message": "Your KYC submission has been received and is awaiting review. You will receive an email once approved."
  },
  "meta": { "request_id": "req_abc123", "timestamp": "2026-09-04T09:12:33Z" }
}
```

`data.kyb` is the business-screening signal (`clear` when the entity is clean, `review` when any sanctioned-country or denied-party match fired). It is reviewed alongside the form — it never blocks submission, and a `review` verdict still requires the same human pass. The response omits `kyb` entirely when the screening step could not run (e.g. its non-blocking engine was unreachable, flagged for manual review instead).

## 3. Optional: add a hosted ID verification session

Some operators require a government-ID document plus a selfie-liveness check before they sign off. POST `/api/v1/organization/kyc/idv/session` opens a hosted session with the configured provider; open the returned URL in a browser or hand it to the signer.

```bash cURL theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/organization/kyc/idv/session \
  -H "X-API-Key: $ORBIT_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "redirect_url": "https://your-app.example/kyc/return" }'
```

**Response (200):**

```json theme={null}
{
  "data": {
    "status": "pending",
    "provider": "idv",
    "session_id": "sess_9f2b7c",
    "hosted_url": "https://hosted-idv.example/sessions/sess_9f2b7c",
    "reason": null,
    "created_at": "2026-09-04T09:13:04Z",
    "updated_at": "2026-09-04T09:13:04Z"
  },
  "meta": { "request_id": "req_def456", "timestamp": "2026-09-04T09:13:04Z" }
}
```

Until an operator provisions a vendor, the endpoint answers `503`:

```json theme={null}
{ "error": { "code": "IDV_NOT_CONFIGURED", "message": "Identity verification is not available for this account", "status": 503 } }
```

Check the provider's verdict with `GET /api/v1/organization/kyc/idv/status`. It self-reconciles: as long as the stored session is `pending`, each GET asks the provider for the latest outcome and writes back a terminal transition. A terminal `verified`, `declined`, or `expired` — plus a `reason` when the provider supplies one — joins the review signal the operator weighs next to the submission. It is **never** self-approving: a `verified` identity does NOT approve KYC, and a `declined` outcome does not reject it.

```json theme={null}
{
  "data": {
    "status": "verified",
    "provider": "idv",
    "session_id": "sess_9f2b7c",
    "hosted_url": "https://hosted-idv.example/sessions/sess_9f2b7c",
    "reason": null,
    "created_at": "2026-09-04T09:13:04Z",
    "updated_at": "2026-09-04T09:44:12Z",
    "configured": true
  }
}
```

## 4. Poll for the organization verdict

Poll `GET /api/v1/organization/kyc/status` until the org-level verdict lands. The available states are `not_started`, `pending` (pre-submission transient), `pending_review`, `approved`, and `rejected`; the response also echoes the submitted company fields and `reviewed_at` once a decision was made.

```bash cURL theme={null}
curl https://api.orbit.devotel.io/api/v1/organization/kyc/status \
  -H "X-API-Key: $ORBIT_TEST_KEY"
```

**Response (200):**

```json theme={null}
{
  "data": {
    "status": "pending_review",
    "company_name": "Acme Logistics Ltd.",
    "country": "US",
    "industry": "Logistics",
    "submitted_at": "2026-09-04T09:12:33Z",
    "reviewed_at": null,
    "source": null
  },
  "meta": { "request_id": "req_ghi789", "timestamp": "2026-09-04T09:15:00Z" }
}
```

Old SDK builds and hand-rolled integrations may read the bare `GET /api/v1/organization/kyc` path; it serves the same response shape, so targeting `/kyc/status` is safe while still compatible.

A client-side loop with backoff (pseudocode):

```text theme={null}
attempts = 0
while status not in {"approved", "rejected"}:
    sleep backoff[min(attempts, len(backoff))]   # e.g. 5s, 15s, 60s, 300s
    status = GET /organization/kyc/status
    attempts += 1
if status == "rejected":
    surface the rejection and follow section 6
```

The endpoint is safe to poll from the dashboard or a server — it degrades to a neutral `not_started` read on a transient database blip instead of surfacing a 503, and the next poll self-corrects.

## 5. Re-submitting and the already-approved guard

POSTing the form again is the correct move on a `rejected` org: the write re-stamps `pending_review`, overwrites the form block, and re-runs the business screen with the corrected answers. A still-`pending_review` resubmission is also accepted — it replaces the in-flight profile.

Trying to resubmit an **approved** organization returns **409**:

```json theme={null}
{
  "error": {
    "code": "ALREADY_VERIFIED",
    "message": "KYC verification has already been approved",
    "status": 409
  }
}
```

The same guard applies to an IDV session once the identity is `verified` — a POST to the session endpoint then gets `409 ALREADY_VERIFIED`, and a re-capture only makes sense after the org was rejected and is being re-driven.

## 6. What a rejection means and what to do about it

A rejection is a human verdict — the operator walks the **Devotel operations panel**, reads your form fields plus the business-screening and identity-verification signals, and hits approve or reject. The customer-facing surface never exposes a machine rationale; the decision email names the gap and the fix. Treat `rejected` as actionable:

1. Re-read the submitted fields for accuracy (a mismatched legal name or a thin use-case description is the most common flag).
2. Fix any business-screening (`kyb`) match and any declined IDV result.
3. Res-submit with the corrected data — the endpoint accepts it and the queue re-orders.

If the rejection is clearly an error — for example a typo in the operator panel rather than in your data — file through [support](/troubleshooting/auth-and-api-keys) with the account id and the `req_*` id from the status poll; the review queue owner can re-open the file and approve it from the operator side.

### What this gate is NOT: per-number sender documents

Organization KYC sits beside the per-number document bundles that carriers ask for per sender — the latter (business registration, address proof, identity) cover a specific number you own and follow a different review loop in **Compliance → Documents**. Approving the organization does not settle a number-level bundle, and vice versa. See the [per-number KYC documents guide](/compliance/documents-kyc) for that separate register.

## 7. Once approved — the remaining go-live gates

Approval flips the organization verdict, so `GET /organization/kyc/status` returns `approved`. Finish the two remaining gates from [the go-live checklist](/guides/go-live-checklist):

* **Mint the live key.** Under **Settings → API Keys**, create a secret with the `dv_live_sk_` prefix and swap it in for the sandbox `dv_test_sk_` key — request shapes are identical, so no code rewrite is needed.
* **Fund the balance.** Add funds under **Settings → Billing**; SMS, WhatsApp, and voice all deduct from this wallet, and live sends fail with a billing error while the balance is empty.
* **US SMS: layer 10DLC.** If the destination includes US long codes, finish the [10DLC brand + campaign registration](/guides/10dlc-registration). KYC approval alone never substitutes for carrier registration; both gates must be green before a US SMS send leaves the sandbox tier.

Once the two hard gates plus any channel registrations pass, the live send behaves exactly like the sandbox send — same endpoint, same webhook envelope, no further approval loop.
