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

# Onboarding Clients via API

> Programmatically create partner invitations to let clients connect their WhatsApp Business accounts

This guide shows you how to automate client onboarding using the Zavu API to create partner invitations programmatically.

## Overview

The Partner Invitations API allows you to:

* Create invitation links programmatically
* Integrate client onboarding into your own application
* Optionally pre-assign phone numbers to invitations
* Track invitation status and completion

## Creating an Invitation

### Basic Invitation

Create an invitation where the client provides their own phone number:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Zavudev from '@zavudev/sdk';

  const zavu = new Zavudev({
    apiKey: process.env["ZAVUDEV_API_KEY"],
  });

  const invitation = await zavu.invitations.create({
    clientName: 'Acme Corp',
    clientEmail: 'contact@acme.com',
    expiresInDays: 7,
  });

  console.log('Invitation URL:', invitation.url);
  console.log('Token:', invitation.token);
  console.log('Expires:', invitation.expiresAt);
  ```

  ```python Python theme={null}
  import os
  from zavudev import Zavudev

  zavu = Zavudev(
      api_key=os.environ.get("ZAVUDEV_API_KEY"),
  )

  invitation = zavu.invitations.create(
      client_name="Acme Corp",
      client_email="contact@acme.com",
      expires_in_days=7
  )

  print("Invitation URL:", invitation.url)
  print("Token:", invitation.token)
  print("Expires:", invitation.expires_at)
  ```

  ```ruby Ruby theme={null}
  require "zavudev"

  client = Zavudev::Client.new(api_key: ENV["ZAVUDEV_API_KEY"])

  invitation = client.invitations.create(
    client_name: "Acme Corp",
    client_email: "contact@acme.com",
    expires_in_days: 7
  )

  puts "Invitation URL: #{invitation.url}"
  puts "Token: #{invitation.token}"
  puts "Expires: #{invitation.expires_at}"
  ```

  ```go Go theme={null}
  client := zavudev.NewClient(zavudev.WithAPIKey(os.Getenv("ZAVUDEV_API_KEY")))

  invitation, _ := client.Invitations.Create(context.TODO(), zavudev.InvitationCreateParams{
  	ClientName:    zavudev.String("Acme Corp"),
  	ClientEmail:   zavudev.String("contact@acme.com"),
  	ExpiresInDays: zavudev.Int(7),
  })

  fmt.Println("Invitation URL:", invitation.URL)
  fmt.Println("Token:", invitation.Token)
  fmt.Println("Expires:", invitation.ExpiresAt)
  ```

  ```php PHP theme={null}
  $client = new Zavudev\Client(apiKey: getenv('ZAVUDEV_API_KEY'));

  $invitation = $client->invitations->create([
      'clientName' => 'Acme Corp',
      'clientEmail' => 'contact@acme.com',
      'expiresInDays' => 7,
  ]);

  echo "Invitation URL: {$invitation->url}\n";
  echo "Token: {$invitation->token}\n";
  echo "Expires: {$invitation->expiresAt}\n";
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/invitations \
    -H "Authorization: Bearer $ZAVU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "clientName": "Acme Corp",
      "clientEmail": "contact@acme.com",
      "expiresInDays": 7
    }'
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "invitation": {
    "id": "jh7am5bng9p3v2x1k4r8",
    "url": "https://dashboard.zavu.dev/invite/abc123xyz",
    "token": "abc123xyz",
    "clientName": "Acme Corp",
    "clientEmail": "contact@acme.com",
    "phoneNumberId": null,
    "status": "pending",
    "senderId": null,
    "expiresAt": "2025-01-22T00:00:00.000Z",
    "createdAt": "2025-01-15T12:00:00.000Z"
  }
}
```

### With Pre-assigned Phone Number

Pre-assign a Zavu phone number that the client will register with WhatsApp:

<CodeGroup>
  ```typescript TypeScript theme={null}
  // First, purchase a phone number
  const phoneNumber = await zavu.phoneNumbers.purchase({
    phoneNumber: '+14155551234',
    name: 'Client: Acme Corp',
  });

  // Then create the invitation with the phone number
  const invitation = await zavu.invitations.create({
    clientName: 'Acme Corp',
    clientEmail: 'contact@acme.com',
    phoneNumberId: phoneNumber.id,
    expiresInDays: 14,
  });

  console.log('Invitation URL:', invitation.url);
  ```

  ```python Python theme={null}
  # First, purchase a phone number
  phone_number = client.phone_numbers.purchase(
      phone_number="+14155551234",
      name="Client: Acme Corp"
  )

  # Then create the invitation with the phone number
  invitation = client.invitations.create(
      client_name="Acme Corp",
      client_email="contact@acme.com",
      phone_number_id=phone_number.id,
      expires_in_days=14
  )

  print("Invitation URL:", invitation.url)
  ```

  ```ruby Ruby theme={null}
  # First, purchase a phone number
  phone_number = client.phone_numbers.purchase(
    phone_number: "+14155551234",
    name: "Client: Acme Corp"
  )

  # Then create the invitation with the phone number
  invitation = client.invitations.create(
    client_name: "Acme Corp",
    client_email: "contact@acme.com",
    phone_number_id: phone_number.id,
    expires_in_days: 14
  )

  puts "Invitation URL: #{invitation.url}"
  ```

  ```go Go theme={null}
  // First, purchase a phone number
  phoneNumber, _ := client.PhoneNumbers.Purchase(context.TODO(), zavudev.PhoneNumberPurchaseParams{
  	PhoneNumber: zavudev.String("+14155551234"),
  	Name:        zavudev.String("Client: Acme Corp"),
  })

  // Then create the invitation with the phone number
  invitation, _ := client.Invitations.Create(context.TODO(), zavudev.InvitationCreateParams{
  	ClientName:    zavudev.String("Acme Corp"),
  	ClientEmail:   zavudev.String("contact@acme.com"),
  	PhoneNumberID: zavudev.String(phoneNumber.ID),
  	ExpiresInDays: zavudev.Int(14),
  })

  fmt.Println("Invitation URL:", invitation.URL)
  ```

  ```php PHP theme={null}
  // First, purchase a phone number
  $phoneNumber = $client->phoneNumbers->purchase([
      'phoneNumber' => '+14155551234',
      'name' => 'Client: Acme Corp',
  ]);

  // Then create the invitation with the phone number
  $invitation = $client->invitations->create([
      'clientName' => 'Acme Corp',
      'clientEmail' => 'contact@acme.com',
      'phoneNumberId' => $phoneNumber->id,
      'expiresInDays' => 14,
  ]);

  echo "Invitation URL: {$invitation->url}\n";
  ```

  ```bash cURL theme={null}
  # First, purchase a phone number
  curl -X POST https://api.zavu.dev/v1/phone-numbers \
    -H "Authorization: Bearer $ZAVU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "phoneNumber": "+14155551234",
      "name": "Client: Acme Corp"
    }'

  # Then create the invitation with the phone number ID
  curl -X POST https://api.zavu.dev/v1/invitations \
    -H "Authorization: Bearer $ZAVU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "clientName": "Acme Corp",
      "clientEmail": "contact@acme.com",
      "phoneNumberId": "pn_abc123",
      "expiresInDays": 14
    }'
  ```
</CodeGroup>

<Info>
  When you pre-assign a phone number, the invitation page will show the number and automatically display the verification code when it's received via phone call.
</Info>

### Create Parameters

| Parameter       | Type    | Required | Description                                 |
| --------------- | ------- | -------- | ------------------------------------------- |
| `clientName`    | string  | No       | Name to identify the client (max 100 chars) |
| `clientEmail`   | string  | No       | Client's email address                      |
| `clientPhone`   | string  | No       | Client's phone number (E.164 format)        |
| `phoneNumberId` | string  | No       | Pre-assign a Zavu phone number              |
| `expiresInDays` | integer | No       | Link validity in days (1-30, default: 7)    |

## Listing Invitations

Retrieve all invitations for your project:

<CodeGroup>
  ```typescript TypeScript theme={null}
  // List all invitations
  const { items } = await zavu.invitations.list({});

  // Filter by status
  const pending = await zavu.invitations.list({
    status: 'pending',
  });

  for (const inv of pending.items) {
    console.log(inv.clientName, inv.status, inv.url);
  }
  ```

  ```python Python theme={null}
  # List all invitations
  result = client.invitations.list()

  # Filter by status
  pending = client.invitations.list(status="pending")

  for inv in pending.items:
      print(inv.client_name, inv.status, inv.url)
  ```

  ```ruby Ruby theme={null}
  # List all invitations
  result = client.invitations.list

  # Filter by status
  pending = client.invitations.list(status: "pending")

  pending.items.each do |inv|
    puts "#{inv.client_name} #{inv.status} #{inv.url}"
  end
  ```

  ```go Go theme={null}
  // List all invitations
  result, _ := client.Invitations.List(context.TODO(), zavudev.InvitationListParams{})

  // Filter by status
  pending, _ := client.Invitations.List(context.TODO(), zavudev.InvitationListParams{
  	Status: zavudev.String("pending"),
  })

  for _, inv := range pending.Items {
  	fmt.Println(inv.ClientName, inv.Status, inv.URL)
  }
  ```

  ```php PHP theme={null}
  // List all invitations
  $result = $client->invitations->list();

  // Filter by status
  $pending = $client->invitations->list(['status' => 'pending']);

  foreach ($pending->items as $inv) {
      echo "{$inv->clientName} {$inv->status} {$inv->url}\n";
  }
  ```

  ```bash cURL theme={null}
  # List all invitations
  curl "https://api.zavu.dev/v1/invitations" \
    -H "Authorization: Bearer $ZAVU_API_KEY"

  # Filter by status
  curl "https://api.zavu.dev/v1/invitations?status=pending" \
    -H "Authorization: Bearer $ZAVU_API_KEY"
  ```
</CodeGroup>

### List Parameters

| Parameter | Type    | Description                                                                     |
| --------- | ------- | ------------------------------------------------------------------------------- |
| `status`  | string  | Filter by status: `pending`, `in_progress`, `completed`, `expired`, `cancelled` |
| `limit`   | integer | Results per page (default: 50, max: 100)                                        |
| `cursor`  | string  | Pagination cursor                                                               |

## Getting Invitation Details

Retrieve details for a specific invitation:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const invitation = await zavu.invitations.get({
    invitationId: 'jh7am5bng9p3v2x1k4r8',
  });

  console.log('Status:', invitation.status);
  console.log('Client:', invitation.clientName);

  if (invitation.status === 'completed') {
    console.log('Sender ID:', invitation.senderId);
  }
  ```

  ```python Python theme={null}
  invitation = client.invitations.get(
      invitation_id="jh7am5bng9p3v2x1k4r8"
  )

  print("Status:", invitation.status)
  print("Client:", invitation.client_name)

  if invitation.status == "completed":
      print("Sender ID:", invitation.sender_id)
  ```

  ```ruby Ruby theme={null}
  invitation = client.invitations.get(
    invitation_id: "jh7am5bng9p3v2x1k4r8"
  )

  puts "Status: #{invitation.status}"
  puts "Client: #{invitation.client_name}"

  if invitation.status == "completed"
    puts "Sender ID: #{invitation.sender_id}"
  end
  ```

  ```go Go theme={null}
  invitation, _ := client.Invitations.Get(context.TODO(), "jh7am5bng9p3v2x1k4r8")

  fmt.Println("Status:", invitation.Status)
  fmt.Println("Client:", invitation.ClientName)

  if invitation.Status == "completed" {
  	fmt.Println("Sender ID:", invitation.SenderID)
  }
  ```

  ```php PHP theme={null}
  $invitation = $client->invitations->get('jh7am5bng9p3v2x1k4r8');

  echo "Status: {$invitation->status}\n";
  echo "Client: {$invitation->clientName}\n";

  if ($invitation->status === 'completed') {
      echo "Sender ID: {$invitation->senderId}\n";
  }
  ```

  ```bash cURL theme={null}
  curl "https://api.zavu.dev/v1/invitations/jh7am5bng9p3v2x1k4r8" \
    -H "Authorization: Bearer $ZAVU_API_KEY"
  ```
</CodeGroup>

### Response Fields

| Field           | Type   | Description                              |
| --------------- | ------ | ---------------------------------------- |
| `id`            | string | Unique invitation ID                     |
| `url`           | string | Full invitation URL to share with client |
| `token`         | string | URL token (part of the URL)              |
| `clientName`    | string | Client name                              |
| `clientEmail`   | string | Client email                             |
| `phoneNumberId` | string | Pre-assigned phone number ID (if any)    |
| `status`        | string | Current status                           |
| `senderId`      | string | Created sender ID (when completed)       |
| `expiresAt`     | string | Expiration timestamp                     |
| `viewedAt`      | string | When client first viewed the invitation  |
| `startedAt`     | string | When client started the setup            |
| `completedAt`   | string | When setup was completed                 |

## Cancelling an Invitation

Cancel an invitation to prevent it from being used:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await zavu.invitations.cancel({
    invitationId: 'jh7am5bng9p3v2x1k4r8',
  });
  ```

  ```python Python theme={null}
  client.invitations.cancel(
      invitation_id="jh7am5bng9p3v2x1k4r8"
  )
  ```

  ```ruby Ruby theme={null}
  client.invitations.cancel(
    invitation_id: "jh7am5bng9p3v2x1k4r8"
  )
  ```

  ```go Go theme={null}
  client.Invitations.Cancel(context.TODO(), "jh7am5bng9p3v2x1k4r8")
  ```

  ```php PHP theme={null}
  $client->invitations->cancel('jh7am5bng9p3v2x1k4r8');
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.zavu.dev/v1/invitations/jh7am5bng9p3v2x1k4r8/cancel" \
    -H "Authorization: Bearer $ZAVU_API_KEY"
  ```
</CodeGroup>

<Note>
  You cannot cancel a completed invitation. Once a sender is created, manage it through the Senders API instead.
</Note>

## Invitation Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Create invitation
    pending --> in_progress: Client starts setup
    pending --> expired: Time expires
    pending --> cancelled: Cancel invitation
    in_progress --> completed: Setup successful
    in_progress --> expired: Time expires
    in_progress --> cancelled: Cancel invitation
    completed --> [*]: Sender created
    expired --> [*]
    cancelled --> [*]
```

## Integration Examples

### SaaS Onboarding Flow

Integrate WhatsApp setup into your SaaS onboarding:

```typescript theme={null}
async function onboardNewCustomer(customer: Customer) {
  // 1. Create the customer in your system
  await createCustomer(customer);

  // 2. Create a Zavu invitation
  const invitation = await zavu.invitations.create({
    clientName: customer.companyName,
    clientEmail: customer.email,
    expiresInDays: 14,
  });

  // 3. Send onboarding email with the invitation link
  await sendEmail({
    to: customer.email,
    subject: 'Connect Your WhatsApp Business',
    body: `
      Welcome to our platform!

      To enable WhatsApp messaging, please complete the setup:
      ${invitation.url}

      This link expires in 14 days.
    `,
  });

  // 4. Store the invitation ID for tracking
  await saveInvitationId(customer.id, invitation.id);
}
```

### Polling for Completion

Check if a client has completed the setup:

```typescript theme={null}
async function checkInvitationStatus(invitationId: string) {
  const invitation = await zavu.invitations.get({ invitationId });

  switch (invitation.status) {
    case 'completed':
      console.log('Client connected! Sender ID:', invitation.senderId);
      // Start sending messages through this sender
      break;
    case 'pending':
    case 'in_progress':
      console.log('Still waiting for client to complete setup');
      break;
    case 'expired':
      console.log('Invitation expired, creating new one...');
      // Create a new invitation
      break;
    case 'cancelled':
      console.log('Invitation was cancelled');
      break;
  }

  return invitation;
}
```

### Webhook Integration

Set up a webhook to be notified when invitations are completed:

```typescript theme={null}
// In your webhook handler
app.post('/webhooks/zavu', async (req, res) => {
  const event = req.body;

  if (event.type === 'sender.created') {
    // A new sender was created (possibly from an invitation)
    const sender = event.data;
    console.log('New sender:', sender.id, sender.phoneNumber);

    // Update your system
    await updateCustomerWhatsAppStatus(sender.phoneNumber, 'connected');
  }

  res.status(200).send('OK');
});
```

## Error Handling

<CodeGroup>
  ```typescript TypeScript theme={null}
  try {
    const invitation = await zavu.invitations.create({
      clientName: 'Acme Corp',
      phoneNumberId: 'pn_invalid',
    });
  } catch (error) {
    if (error.code === 'not_found') {
      console.error('Phone number not found');
    } else if (error.code === 'invalid_request') {
      console.error('Invalid request:', error.message);
    } else {
      console.error('Unexpected error:', error);
    }
  }
  ```

  ```python Python theme={null}
  from zavu import ZavuError

  try:
      invitation = client.invitations.create(
          client_name="Acme Corp",
          phone_number_id="pn_invalid"
      )
  except ZavuError as e:
      if e.code == "not_found":
          print("Phone number not found")
      elif e.code == "invalid_request":
          print("Invalid request:", e.message)
      else:
          print("Unexpected error:", e)
  ```

  ```ruby Ruby theme={null}
  begin
    invitation = client.invitations.create(
      client_name: "Acme Corp",
      phone_number_id: "pn_invalid"
    )
  rescue Zavudev::Error => e
    case e.code
    when "not_found"
      puts "Phone number not found"
    when "invalid_request"
      puts "Invalid request: #{e.message}"
    else
      puts "Unexpected error: #{e}"
    end
  end
  ```

  ```go Go theme={null}
  invitation, err := client.Invitations.Create(context.TODO(), zavudev.InvitationCreateParams{
  	ClientName:    zavudev.String("Acme Corp"),
  	PhoneNumberID: zavudev.String("pn_invalid"),
  })
  if err != nil {
  	var apiErr *zavudev.Error
  	if errors.As(err, &apiErr) {
  		switch apiErr.Code {
  		case "not_found":
  			fmt.Println("Phone number not found")
  		case "invalid_request":
  			fmt.Println("Invalid request:", apiErr.Message)
  		default:
  			fmt.Println("Unexpected error:", apiErr)
  		}
  	}
  }
  ```

  ```php PHP theme={null}
  try {
      $invitation = $client->invitations->create([
          'clientName' => 'Acme Corp',
          'phoneNumberId' => 'pn_invalid',
      ]);
  } catch (Zavudev\Error $e) {
      if ($e->code === 'not_found') {
          echo "Phone number not found\n";
      } elseif ($e->code === 'invalid_request') {
          echo "Invalid request: {$e->getMessage()}\n";
      } else {
          echo "Unexpected error: {$e}\n";
      }
  }
  ```
</CodeGroup>

### Common Errors

| Code                      | Description                              |
| ------------------------- | ---------------------------------------- |
| `not_found`               | Phone number or invitation not found     |
| `invalid_request`         | Invalid parameters                       |
| `phone_number_wrong_team` | Phone number belongs to a different team |
| `already_completed`       | Cannot cancel a completed invitation     |

## Best Practices

<CardGroup cols={2}>
  <Card title="Store Invitation IDs" icon="database">
    Save the invitation ID in your database to track status and link to your customer records.
  </Card>

  <Card title="Set Reminders" icon="bell">
    If an invitation is pending for several days, send a reminder to your client.
  </Card>

  <Card title="Handle Expiration" icon="clock">
    Monitor for expired invitations and automatically create new ones if needed.
  </Card>

  <Card title="Use Webhooks" icon="webhook">
    Set up webhooks to get real-time notifications when senders are created.
  </Card>
</CardGroup>

## Rate Limits

The Invitations API has the following rate limits:

| Endpoint          | Limit               |
| ----------------- | ------------------- |
| Create invitation | 100 requests/minute |
| List invitations  | 200 requests/minute |
| Get invitation    | 200 requests/minute |
| Cancel invitation | 100 requests/minute |

## Next Steps

<CardGroup cols={2}>
  <Card title="Dashboard Guide" icon="browser" href="/guides/partner-invitations/dashboard">
    Learn to create invitations from the dashboard
  </Card>

  <Card title="Senders API" icon="paper-plane" href="/concepts/senders">
    Work with senders created from invitations
  </Card>

  <Card title="Send WhatsApp Messages" icon="message" href="/guides/whatsapp/overview">
    Start sending messages through your new senders
  </Card>

  <Card title="Phone Numbers" icon="phone" href="/guides/phone-numbers/purchasing">
    Purchase phone numbers to pre-assign to invitations
  </Card>
</CardGroup>
