# Twilio Client

Send SMS messages, make calls, and interact with Twilio's communication APIs.

## Methods

| Method                                   | Description                                        |
| ---------------------------------------- | -------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Twilio API request with schema validation |

## Usage

### Send an SMS

```typescript
import { api, z, twilio } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_TWILIO = "a1b2c3d4-5678-90ab-cdef-twilio000001";

const MessageResponseSchema = z.object({
  sid: z.string(),
  date_created: z.string(),
  date_updated: z.string(),
  date_sent: z.string().nullable(),
  account_sid: z.string(),
  to: z.string(),
  from: z.string(),
  body: z.string(),
  status: z.string(),
  direction: z.string(),
  price: z.string().nullable(),
  price_unit: z.string().nullable(),
});

export default api({
  integrations: {
    twilio: twilio(PROD_TWILIO),
  },
  name: "TwilioExample",
  input: z.object({
    to: z.string(),
    message: z.string(),
  }),
  output: z.object({
    messageSid: z.string(),
    status: z.string(),
  }),
  async run(ctx, { to, message }) {
    // Note: Account SID is part of the URL path
    const accountSid = ctx.env.TWILIO_ACCOUNT_SID;

    const result = await ctx.integrations.twilio.apiRequest(
      {
        method: "POST",
        path: `/2010-04-01/Accounts/${accountSid}/Messages.json`,
        body: {
          To: to,
          From: "+15551234567", // Your Twilio phone number
          Body: message,
        },
      },
      { response: MessageResponseSchema },
    );

    return { messageSid: result.sid, status: result.status };
  },
});
```

### Send MMS (with Media)

```typescript
const result = await ctx.integrations.twilio.apiRequest(
  {
    method: "POST",
    path: `/2010-04-01/Accounts/${accountSid}/Messages.json`,
    body: {
      To: "+15559876543",
      From: "+15551234567",
      Body: "Check out this image!",
      MediaUrl: "https://example.com/image.jpg",
    },
  },
  { response: MessageResponseSchema },
);
```

### List Messages

```typescript
const ListMessagesResponseSchema = z.object({
  messages: z.array(MessageResponseSchema),
  uri: z.string(),
  first_page_uri: z.string(),
  next_page_uri: z.string().nullable(),
  previous_page_uri: z.string().nullable(),
  page: z.number(),
  page_size: z.number(),
});

const result = await ctx.integrations.twilio.apiRequest(
  {
    method: "GET",
    path: `/2010-04-01/Accounts/${accountSid}/Messages.json`,
    params: {
      PageSize: 50,
      To: "+15559876543", // Filter by recipient
    },
  },
  { response: ListMessagesResponseSchema },
);

result.messages.forEach((msg) => {
  console.log(`${msg.from} -> ${msg.to}: ${msg.body}`);
});
```

### Make a Voice Call

```typescript
const CallResponseSchema = z.object({
  sid: z.string(),
  date_created: z.string(),
  date_updated: z.string(),
  account_sid: z.string(),
  to: z.string(),
  from: z.string(),
  status: z.string(),
  direction: z.string(),
  duration: z.string().nullable(),
  price: z.string().nullable(),
});

const result = await ctx.integrations.twilio.apiRequest(
  {
    method: "POST",
    path: `/2010-04-01/Accounts/${accountSid}/Calls.json`,
    body: {
      To: "+15559876543",
      From: "+15551234567",
      Url: "http://demo.twilio.com/docs/voice.xml", // TwiML URL
    },
  },
  { response: CallResponseSchema },
);

console.log(`Call SID: ${result.sid}, Status: ${result.status}`);
```

### Lookup Phone Number

```typescript
const LookupResponseSchema = z.object({
  calling_country_code: z.string(),
  country_code: z.string(),
  phone_number: z.string(),
  national_format: z.string(),
  valid: z.boolean(),
  caller_name: z
    .object({
      caller_name: z.string().nullable(),
      caller_type: z.string().nullable(),
    })
    .nullable()
    .optional(),
  carrier: z
    .object({
      name: z.string().nullable(),
      type: z.string().nullable(),
      mobile_country_code: z.string().nullable(),
      mobile_network_code: z.string().nullable(),
    })
    .nullable()
    .optional(),
});

const result = await ctx.integrations.twilio.apiRequest(
  {
    method: "GET",
    path: `/v2/PhoneNumbers/+15559876543`,
    params: {
      Fields: "carrier,caller_name",
    },
  },
  { response: LookupResponseSchema },
);

console.log(`Number: ${result.national_format}, Valid: ${result.valid}`);
if (result.carrier) {
  console.log(`Carrier: ${result.carrier.name}`);
}
```

### Get Message Status

```typescript
const result = await ctx.integrations.twilio.apiRequest(
  {
    method: "GET",
    path: `/2010-04-01/Accounts/${accountSid}/Messages/${messageSid}.json`,
  },
  { response: MessageResponseSchema },
);

console.log(`Message status: ${result.status}`);
// Statuses: queued, sending, sent, delivered, undelivered, failed
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await twilio.sendSms({ ... });
await twilio.makeCall({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.twilio.apiRequest(
  { method: "POST", path: `/2010-04-01/Accounts/${accountSid}/Messages.json`, body: { ... } },
  { response: MessageResponseSchema }
);
```

### Account SID in URL Path

Twilio requires the Account SID in the URL path:

```typescript
// WRONG - Missing account SID
await ctx.integrations.twilio.apiRequest(
  {
    method: "POST",
    path: "/Messages.json",
    body: { ... },
  },
  { response: schema }
);

// CORRECT - Include account SID in path
await ctx.integrations.twilio.apiRequest(
  {
    method: "POST",
    path: `/2010-04-01/Accounts/${accountSid}/Messages.json`,
    body: { ... },
  },
  { response: schema }
);
```

### Parameter Naming (PascalCase)

Twilio uses PascalCase for parameters:

```typescript
// WRONG - lowercase parameters
const body = {
  to: "+15559876543",
  from: "+15551234567",
  body: "Hello",
};

// CORRECT - PascalCase parameters
const body = {
  To: "+15559876543",
  From: "+15551234567",
  Body: "Hello",
};
```

### Phone Number Format

Use E.164 format for phone numbers:

```typescript
// WRONG - Various formats
const numbers = ["(555) 123-4567", "555-123-4567", "5551234567"];

// CORRECT - E.164 format
const numbers = ["+15551234567", "+442071234567"];
```

### API Versions

Different APIs use different path prefixes:

```typescript
// Messaging/Voice API (older)
const path = `/2010-04-01/Accounts/${accountSid}/Messages.json`;

// Lookup API (v2)
const path = `/v2/PhoneNumbers/${phoneNumber}`;

// Verify API (v2)
const path = `/v2/Services/${serviceSid}/Verifications`;
```

### Message Status Callbacks

For delivery receipts, set a StatusCallback URL:

```typescript
const result = await ctx.integrations.twilio.apiRequest(
  {
    method: "POST",
    path: `/2010-04-01/Accounts/${accountSid}/Messages.json`,
    body: {
      To: "+15559876543",
      From: "+15551234567",
      Body: "Hello",
      StatusCallback: "https://your-app.com/twilio/status",
    },
  },
  { response: MessageResponseSchema },
);
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.twilio.apiRequest(
    { method: "POST", path: `...`, body: { ... } },
    { response: MessageResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Twilio API Documentation](https://www.twilio.com/docs/api)
- [Send SMS](https://www.twilio.com/docs/sms/api/message-resource)
- [Make Calls](https://www.twilio.com/docs/voice/api/call-resource)
- [Lookup API](https://www.twilio.com/docs/lookup/v2-api)
- [Error Codes](https://www.twilio.com/docs/api/errors)
