# Regulatory Requirements

> Fulfill regulatory requirements for phone number purchases in certain countries

Some phone numbers require regulatory information before they can be used: an address, an identity or business document, or text such as a business name. Which numbers do is decided per number by the carrier, not by a fixed list of countries, and the purchase checks it for the exact number you buy, before anything is charged.

## Overview

To buy a number with regulatory requirements:

1. **Check requirements** for the number you want to buy
2. **Create addresses** for any `address` requirement
3. **Upload documents** for any `document` requirement
4. **Purchase** the number with its `type`, passing every requirement as `regulatoryRequirements`
5. **Wait for review**: the number is yours immediately with `regulatoryStatus: pending_review`, and can send messages and place calls once it is `approved`
6. **Assign it to a sender**, before or after approval

The information is kept for your project under the number's country and type. A later purchase in the same country and type may omit `regulatoryRequirements`, as long as what is kept still covers that number's requirements.

## Checking Requirements

Before purchasing, check what requirements apply. Pass `phoneNumber` with a number from `GET /v1/phone-numbers/available` to get the requirements for that exact number, which is what the purchase checks. Encode its `+` as `%2B`. `countryCode` and `type` give the requirements for a country instead:

```bash
curl "https://api.zavu.dev/v1/phone-numbers/requirements?phoneNumber=%2B4930123456" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

An empty `items` array means the number needs nothing: buy it normally. When the number's own requirements cannot be resolved, the list for its country and `type` is returned instead, and the purchase checks the same list. If the requirements cannot be retrieved at all, the API answers `502` with code `requirements_unavailable` instead of an empty list: retry rather than purchasing without them. For US and Canadian numbers a purchase does not wait on this lookup: they are sold as numbers without requirements.

The SDKs below take `countryCode` and `type`; they have no per-number variant yet:

```typescript
const requirements = await zavu.phoneNumbers.requirements({
  countryCode: "DE",
  type: "local",
});

for (const req of requirements.items) {
  console.log("Requirement:", req.id);
  for (const type of req.requirementTypes) {
    console.log("  -", type.name, `(${type.type})`);
    console.log("   ", type.description);
  }
}
```

```bash
curl "https://api.zavu.dev/v1/phone-numbers/requirements?countryCode=DE&type=local" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### Requirement Types

| Type | Description |
|------|-------------|
| `address` | An address: create one and pass its `id` |
| `document` | An identity or business document: upload one and pass its `id` |
| `textual` | Text (e.g. a business name): pass the text itself |
| `action` | An instruction to follow; send no entry for it |

### Response

```json
{
  "items": [
    {
      "id": "+4930123456",
      "countryCode": "DE",
      "phoneNumberType": "local",
      "action": "ordering",
      "requirementTypes": [
        {
          "id": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10",
          "name": "Address",
          "description": "An address within Germany",
          "type": "address"
        },
        {
          "id": "3f9e2d41-7b6a-4c1e-8d52-9a0b1c2d3e4f",
          "name": "Identity Document",
          "description": "Government-issued ID",
          "type": "document"
        }
      ]
    }
  ]
}
```

For a `phoneNumber` request, `id` is that phone number. For a country request it identifies the requirement group. `acceptanceCriteria`, when present, carries `allowedValues` on a `phoneNumber` request, and `minLength`, `maxLength` and `allowedValues` on a country request.

## Creating Addresses

If an address requirement exists, create an address first:

```typescript
const created = await zavu.addresses.create({
  firstName: "John",
  lastName: "Doe",
  streetAddress: "Unter den Linden 77",
  locality: "Berlin",
  administrativeArea: "Berlin",
  postalCode: "10117",
  countryCode: "DE",
});

console.log("Address ID:", created.address.id);
console.log("Status:", created.address.status);
```

```bash
curl -X POST https://api.zavu.dev/v1/addresses \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "John",
    "lastName": "Doe",
    "streetAddress": "Unter den Linden 77",
    "locality": "Berlin",
    "administrativeArea": "Berlin",
    "postalCode": "10117",
    "countryCode": "DE"
  }'
```

### Address Fields

> **Warning:**
> `firstName` and `lastName` are required. Requests that omit them are refused
> with `400 invalid_request`: the carrier will not register an address without
> a named person, and an address it refuses cannot be used to buy a number.
> The SDKs still type both as optional until their next release, so a call that
> leaves them out compiles and fails at runtime.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `firstName` | string | Yes | First name of the person the address is registered to |
| `lastName` | string | Yes | Last name of the person the address is registered to |
| `businessName` | string | No | Business name, for a business address. Defaults to the person's full name |
| `streetAddress` | string | Yes | Street address line 1 |
| `extendedAddress` | string | No | Street address line 2 (apartment, suite, etc.) |
| `locality` | string | Yes | City or town |
| `administrativeArea` | string | No | State, province, or region |
| `postalCode` | string | Yes | Postal or ZIP code |
| `countryCode` | string | Yes | Two-letter ISO country code |

### Address Status

A new address is returned with `status: pending` and stays that way. You can use its `id` in a purchase right away, in the same project it was created in. Review happens on the purchase, not on the address: follow it with the number's `regulatoryStatus`. An address marked `rejected` is refused in a purchase.

## Uploading Documents

If a document requirement exists, upload the required document:

```typescript
// Step 1: Get upload URL
const { uploadUrl } = await zavu.regulatoryDocuments.uploadURL();

// Step 2: Upload the file to the returned URL
const uploadResponse = await fetch(uploadUrl, {
  method: "POST",
  headers: { "Content-Type": "image/jpeg" },
  body: fileBuffer,
});
const { storageId } = await uploadResponse.json();

// Step 3: Create document record with the storageId
const created = await zavu.regulatoryDocuments.create({
  name: "John Doe Passport",
  documentType: "passport",
  storageId: storageId,
  mimeType: "image/jpeg",
  fileSize: fileBuffer.byteLength,
});

console.log("Document ID:", created.document.id);
console.log("Status:", created.document.status);
```

```bash
# Step 1: Get upload URL
curl -X POST https://api.zavu.dev/v1/documents/upload-url \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"

# Step 2: Upload file to the returned URL (returns storageId)
STORAGE_ID=$(curl -X POST "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @passport.jpg | jq -r '.storageId')

# Step 3: Create document record
curl -X POST https://api.zavu.dev/v1/documents \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"John Doe Passport\",
    \"documentType\": \"passport\",
    \"storageId\": \"$STORAGE_ID\",
    \"mimeType\": \"image/jpeg\",
    \"fileSize\": 102400
  }"
```

### Supported Document Types

| Type | Description |
|------|-------------|
| `passport` | Government-issued passport |
| `national_id` | National identity card |
| `drivers_license` | Driver's license |
| `utility_bill` | Utility bill (for proof of address) |
| `tax_id` | Tax identification document |
| `business_registration` | Business registration certificate |
| `proof_of_address` | Bank statement or official letter |
| `other` | Other document type |

### File Requirements

The API does not check the file's format or size; the dashboard uploader accepts the following, so stay within it:

- **Formats**: JPEG, PNG, or PDF
- **Max size**: 10MB
- **Quality**: Clear, readable scan or photo
- **Validity**: Document must not be expired

### Document Status

A new document is returned with `status: uploaded` and stays that way. You can use its `id` in a purchase right away, in the project it was uploaded to. Review happens on the purchase, not on the document: follow it with the number's `regulatoryStatus`. A document marked `rejected` is refused in a purchase.

## Purchasing with Requirements

Pass one entry per requirement except `action` ones: `requirementType` is the `requirementTypes[].id` from the requirements response, and `fieldValue` is the address `id`, the document `id`, or the text. `type` is required alongside, the type you searched the number under.

Every required id must be present, once, no unknown id may be sent, and addresses and documents must belong to the project making the purchase and must not be rejected. Otherwise the purchase is refused with `400 invalid_request`, naming the requirement, before anything is charged. The same happens if an address or document cannot be registered for review.

```bash
curl -X POST https://api.zavu.dev/v1/phone-numbers \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+4930123456",
    "name": "Germany Line",
    "type": "local",
    "regulatoryRequirements": [
      { "requirementType": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10", "fieldValue": "<address id>" },
      { "requirementType": "3f9e2d41-7b6a-4c1e-8d52-9a0b1c2d3e4f", "fieldValue": "<document id>" }
    ]
  }'
```

> **Note:**
> `regulatoryRequirements` and `type` are new on this endpoint and not yet in the SDKs. Use the REST call above until the SDKs are updated.

The response is the purchased number. It is billed from purchase, and cannot send messages or place calls until review approves it:

```json
{
  "phoneNumber": {
    "id": "pn_abc123",
    "phoneNumber": "+4930123456",
    "status": "active",
    "regulatoryStatus": "pending_review"
  }
}
```

The status is re-checked every 6 hours. Poll `GET /v1/phone-numbers/{phoneNumberId}` until `regulatoryStatus` is `approved` or `rejected`.

## Assigning a Sender

Assign the number to a sender to use it, before or after approval:

```bash
curl -X PATCH https://api.zavu.dev/v1/phone-numbers/pn_abc123 \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "senderId": "snd_abc123" }'
```

Assigned while `pending_review`, the number is recorded on the sender and connected when it is approved; if that connection fails it is retried until it succeeds. A sender created over the API is set up for SMS as part of the assignment. A `rejected` number cannot be assigned: the call answers `400`. A number that stays `pending_review` for long may be waiting on information the API cannot supply: contact support.

## Reusing Submitted Information

If the number needs regulatory information and the request carries none, the purchase reuses what your project submitted before for the same country and type, but only when it still covers every requirement of the new number and every address and document in it belongs to the project. Otherwise it is refused with `400 regulatory_compliance_required`, listing the missing requirements in `details.missingRequirements`, and nothing is charged.

## Managing Addresses

### List Addresses

```typescript
const result = await zavu.addresses.list();

for (const address of result.items) {
  console.log(address.id, address.streetAddress, address.status);
}
```

```bash
curl "https://api.zavu.dev/v1/addresses" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### Delete Address

```typescript
await zavu.addresses.delete("addr_abc123");
```

```bash
curl -X DELETE https://api.zavu.dev/v1/addresses/addr_abc123 \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

## Managing Documents

### List Documents

```typescript
const result = await zavu.regulatoryDocuments.list();

for (const doc of result.items) {
  console.log(doc.id, doc.name, doc.status);
}
```

```bash
curl "https://api.zavu.dev/v1/documents" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### Delete Document

```typescript
await zavu.regulatoryDocuments.delete("doc_xyz789");
```

```bash
curl -X DELETE https://api.zavu.dev/v1/documents/doc_xyz789 \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

## Country-Specific Requirements

Requirements differ by country and number type, and change with regulation. The requirements endpoint is the source of truth: query it for the number you are about to buy rather than relying on a fixed list.

## Best Practices

  - **Prepare Documents First**: Create addresses and upload documents before the purchase call, so it carries every requirement.
  - **Follow the Review**: A regulated number is billed from purchase but usable only once `regulatoryStatus` is `approved`.
  - **Keep Documents Current**: Ensure documents are not expired and information is up to date.
  - **Check Requirements Early**: Query requirements during your planning phase to avoid purchase delays.

## Error Handling

| Status | Code | Description |
|--------|------|-------------|
| `400` | `regulatory_compliance_required` | The number needs regulatory information and none was provided or reusable. `details.missingRequirements` lists what to send |
| `400` | `invalid_request` | Malformed request, a missing, unknown or repeated requirement id, an address or document from another project or rejected in review, one that could not be registered for review, `type` missing with `regulatoryRequirements`, or requirements that cannot be looked up for this input |
| `400` | `number_unavailable` | The number is no longer available, or is not listed under the `type` sent |
| `502` | `requirements_unavailable` | The requirements lookup failed. Retry; it does not mean nothing is required. A purchase refused this way is not charged. A purchase of a US or Canadian number does not return it, because it goes ahead as a number without requirements; this endpoint returns it for any country |

## Next Steps

  - [Purchase Phone Numbers](https://www.zavu.dev/docs/guides/phone-numbers/purchasing): Learn about the full purchase process
  - [Create Senders](https://www.zavu.dev/docs/concepts/senders): Set up senders to start messaging
