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

# Testing Setup

> Run reusable PROD_OUTBOUND_XTIME SMS and voice smoke tests.

Use this guide to run one SMS conversation and one voice-call test against the PAM Outbound API with the reusable `PROD_OUTBOUND_XTIME` sandbox client.

This sandbox is for outbound API prospects and partners who need to test direct SMS, voice, scheduling, and optional webhook behavior before their production client configuration is ready.

PAM will provide:

* API base URL
* API key for `PROD_OUTBOUND_XTIME`
* Allowed `fromNumber` values for SMS and voice

You will provide:

* A test recipient phone number
* An optional HTTPS webhook endpoint, if you want callback events

## Setup

Set these values locally:

```bash theme={null}
export PAM_API_BASE="https://oos.pamws.com/api/v1"
export PAM_API_KEY="replace_with_pam_api_key"
export PAM_SMS_FROM_NUMBER="+17034230644"
export PAM_VOICE_FROM_NUMBER="+17032142578"
export PAM_TEST_TO_NUMBER="+15555550199"
export PAM_SMS_TEST_RUN_ID="prod-outbound-xtime-sms-smoke-$(date +%s)"
```

All phone numbers must use E.164 format.

All `dynamicVariables` values must be strings.

For managed PAM agents, include the full vehicle identity:

* `vin`
* `vehicle_year`
* `vehicle_make`
* `vehicle_model`

Do not send `contact_phone_number`. PAM derives the customer's phone number from `toNumber`.

## Start a test SMS conversation

Create one SMS conversation:

```bash theme={null}
curl "$PAM_API_BASE/sms-conversations" \
  -X POST \
  -H "Authorization: Bearer $PAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentSlug": "sms-retention",
    "toNumber": "'"$PAM_TEST_TO_NUMBER"'",
    "fromNumber": "'"$PAM_SMS_FROM_NUMBER"'",
    "welcomeMessage": "Hi John, this is Pam from PAM Xtime Test Store. We have a $49.95 oil and filter change special for your 2015 Mercedes-Benz GLK-Class. The offer includes up to 5 quarts of conventional oil, a new oil filter, and a complimentary multi-point inspection. Reply if you want help finding a service time.",
    "dynamicVariables": {
      "contact_first_name": "John",
      "contact_last_name": "Smith",
      "contact_email": "test@example.com",
      "dealership_name": "PAM Xtime Test Store",
      "service_ids": "2323372",
      "service_names": "Replace engine oil and oil filter (6cyl)",
      "offer_description": "$49.95 oil and filter change special, including up to 5 quarts of conventional oil, a new oil filter, and a complimentary multi-point inspection. Taxes, shop supplies, synthetic oil, and diesel vehicles may be extra.",
      "vin": "WDCGG5HB4FG429584",
      "vehicle_year": "2015",
      "vehicle_make": "MERCEDES-BENZ",
      "vehicle_model": "GLK-Class",
      "vehicle_mileage": "100000",
      "transport_type": "WAITER"
    },
    "metadata": {
      "testRun": "prod-outbound-xtime-sms-smoke"
    },
    "externalReferenceId": "'"$PAM_SMS_TEST_RUN_ID"'"
  }'
```

Do not include `idempotencyKey` for manual SMS smoke tests. Use a fresh `externalReferenceId` each time. Reusing an idempotency key can return the original conversation instead of creating a new one.

Expected result:

* The API returns `201 Created`.
* The response includes `data.conversationId`.
* The test phone receives the opening SMS.
* If the test recipient replies, the conversation continues on the same thread.
* If the agent calls scheduling tools, `GET /sms-conversations/{conversationId}` shows the tool invocation and result messages.

Use the returned `conversationId` to fetch state:

```bash theme={null}
export SMS_CONVERSATION_ID="replace_with_conversation_id"

curl "$PAM_API_BASE/sms-conversations/$SMS_CONVERSATION_ID" \
  -H "Authorization: Bearer $PAM_API_KEY"
```

Close the test conversation when finished:

```bash theme={null}
curl "$PAM_API_BASE/sms-conversations/$SMS_CONVERSATION_ID/close" \
  -X POST \
  -H "Authorization: Bearer $PAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"prod_outbound_xtime_sms_smoke_complete"}'
```

## Start a test voice call

Voice calls require an idempotency key. Use a fresh key for each manual test run. Reuse the same key only when retrying the same request after a network timeout.

Create one voice call:

```bash theme={null}
export PAM_CALL_TEST_RUN_ID="prod-outbound-xtime-call-smoke-$(date +%s)"

curl "$PAM_API_BASE/calls" \
  -X POST \
  -H "Authorization: Bearer $PAM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $PAM_CALL_TEST_RUN_ID" \
  -d '{
    "agentSlug": "voice-retention",
    "toNumber": "'"$PAM_TEST_TO_NUMBER"'",
    "fromNumber": "'"$PAM_VOICE_FROM_NUMBER"'",
    "welcomeMessage": "Hi John, this is Pam from PAM Xtime Test Store. I am calling because we have a $49.95 oil and filter change special for your 2015 Mercedes-Benz GLK-Class. The offer includes up to 5 quarts of conventional oil, a new oil filter, and a complimentary multi-point inspection.",
    "dynamicVariables": {
      "contact_first_name": "John",
      "contact_last_name": "Smith",
      "contact_email": "test@example.com",
      "dealership_name": "PAM Xtime Test Store",
      "service_ids": "2323372",
      "service_names": "Replace engine oil and oil filter (6cyl)",
      "offer_description": "$49.95 oil and filter change special, including up to 5 quarts of conventional oil, a new oil filter, and a complimentary multi-point inspection. Taxes, shop supplies, synthetic oil, and diesel vehicles may be extra.",
      "vin": "WDCGG5HB4FG429584",
      "vehicle_year": "2015",
      "vehicle_make": "MERCEDES-BENZ",
      "vehicle_model": "GLK-Class",
      "vehicle_mileage": "100000",
      "transport_type": "WAITER"
    },
    "metadata": {
      "testRun": "prod-outbound-xtime-call-smoke"
    },
    "externalReferenceId": "'"$PAM_CALL_TEST_RUN_ID"'"
  }'
```

Expected result:

* The API returns `202 Accepted`.
* `conversationId` may be `null` at first. PAM creates it after the call provider accepts the call.
* The test phone receives a call from `PAM_VOICE_FROM_NUMBER`.

## Optional webhook testing

You do not need to register a webhook for the basic SMS or voice smoke test.

If you want callback events, first register a webhook:

```bash theme={null}
export PAM_WEBHOOK_URL="https://your-domain.example/pam/webhooks"

curl "$PAM_API_BASE/webhooks" \
  -X POST \
  -H "Authorization: Bearer $PAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "'"$PAM_WEBHOOK_URL"'",
    "events": [
      "call.started",
      "call.ended",
      "call.analyzed",
      "call.failed",
      "sms.message_sent",
      "sms.message_received",
      "sms.message_delivery_updated",
      "sms.tool_call_invocation",
      "sms.tool_call_result"
    ]
  }'
```

The response includes a webhook signing secret once. Store it securely. PAM uses that secret to sign future webhook requests.

Confirm the webhook registration:

```bash theme={null}
curl "$PAM_API_BASE/webhooks" \
  -H "Authorization: Bearer $PAM_API_KEY"
```

To send one conversation's events to a different URL, include `webhookUrl` in the create request:

```json theme={null}
{
  "webhookUrl": "https://your-domain.example/pam/webhooks/test-run-123"
}
```

The override changes only that conversation's destination URL. Event subscriptions and signing still come from the registered webhook.

## Booking test notes

Booking creates a real appointment in the sandbox scheduler. For manual booking tests, use an internal test contact and close the conversation when finished.

The `PROD_OUTBOUND_XTIME` sandbox lookup may not find the test contact by phone number. Include `vin`, `vehicle_year`, `vehicle_make`, and `vehicle_model` so the scheduler still has the full vehicle identity.

## Troubleshooting

| Symptom                                                                            | What to check                                                                                                                         |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `401 unauthorized`                                                                 | Confirm the `Authorization: Bearer` header uses the full API key.                                                                     |
| `400 validation_error`                                                             | Confirm phone numbers are E.164 format and all required dynamic variables are present.                                                |
| `dynamicVariables must include vin, vehicle_year, vehicle_make, and vehicle_model` | Send the full vehicle identity. V1 does not decode VIN-only direct conversations before scheduling.                                   |
| API rejects `contact_phone_number`                                                 | Remove it. PAM derives the customer phone number from `toNumber`.                                                                     |
| No SMS reply events                                                                | Confirm the test phone replied to the same number that sent the opening SMS.                                                          |
| No call lifecycle events                                                           | Confirm the test recipient answered or that a `call.failed` event was received.                                                       |
| No webhook events                                                                  | Confirm the webhook registration includes the event, the endpoint returns a `2xx`, and the endpoint is publicly reachable over HTTPS. |
