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

# Creating a Request

> Submit a single request or a batch of requests with POST /v1/requests.

`POST /v1/requests` creates one or more [requests](/guides/concepts#request) and returns their IDs. The body shape varies based on whether you're submitting a single item or a batch.

## Authentication

Protected routes require a bearer token, including `POST /v1/requests` and the other `/v1/requests*` endpoints. Get one with `GET /v1/auth` (passing your API key and secret as the `Robodialer-API-Key` and `Robodialer-API-Secret` headers). Pass the returned token as `Authorization: Bearer <token>` on every subsequent HTTP request.

Tokens are valid for **1 hour**. Refresh by calling `GET /v1/auth` again; there's no refresh-token flow. For long-running batches or background workers, fetch a fresh token at the start of each work cycle, or refresh on `401` responses.

```python theme={null}
import requests

def fetch_token(api_key: str, api_secret: str) -> str:
    r = requests.get(
        "https://robodialer-service-api-9nc4t1p9.uc.gateway.dev/v1/auth",
        headers={
            "Robodialer-API-Key": api_key,
            "Robodialer-API-Secret": api_secret,
        },
    )
    r.raise_for_status()
    return r.json()["token"]
```

## Single request

Submit a single request by sending a JSON body with the request's fields directly:

```bash theme={null}
curl -X POST https://robodialer-service-api-9nc4t1p9.uc.gateway.dev/v1/requests \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "schemaId": "fWxzG4nqtpHsJxS5Lm3q",
    "inputs": {
      "payerName": "Sample Insurance Co",
      "memberId": "TEST123456789",
      "phoneNumber": "2125551234",
      "providerNpi": "1234567890",
      "dateOfService": "2026-03-15"
    },
    "internalId": "claim_internal_456"
  }'
```

### Response

```json theme={null}
{
  "requestId": "8bF7xK2mP9qR4sT6uV0w",
  "requestBatchId": "pH9kJ2lM4nB6vC8xZ7Qr",
  "internalId": "claim_internal_456"
}
```

If your account is enabled for [**payer phone number lookup**](/guides/payer-resolution) (opt-in: ask your account team), the response carries a `payerLookup` sub-object describing the payer/phone lookup for the request.

```json theme={null}
{
  "requestId": "8bF7xK2mP9qR4sT6uV0w",
  "requestBatchId": "pH9kJ2lM4nB6vC8xZ7Qr",
  "internalId": "claim_internal_456",
  "payerLookup": {
    "inputPayerName": "Sample Insurance Co",
    "inputPhoneNumber": null,
    "matchedPayerName": "Sample Insurance Company, Inc.",
    "matchedPayerPhone": "8005551234",
    "phoneNumberToUse": "8005551234",
    "phoneNumberSource": "superdial"
  }
}
```

`payerLookup` is always present on the response and reports the number that will be dialed (`phoneNumberToUse`) and where it came from (`phoneNumberSource`: `superdial`, `phoneBook`, or `input`). The `matched*` fields are populated only when SuperDial dials its own matched number; if your supplied `phoneNumber` is dialed, they're `null` and `phoneNumberSource` is `"input"`. By default a supplied `phoneNumber` always wins: set [`useMatchedPayerPhone: true`](/guides/payer-resolution#choosing-which-number-to-dial) to dial the matched number instead. The same block is returned on [reads](/guides/reading-requests). See the [Payer Phone Number Lookup guide](/guides/payer-resolution) for the full field reference.

The request begins running immediately. Configure a [webhook](/guides/webhooks) to be notified when it reaches a terminal state, then call [`GET /v1/requests/{requestId}`](/guides/reading-requests#single-request) to fetch the full result.

## Batch

Submit multiple requests in one call by wrapping them in `{ "requests": [...] }`. The response returns one entry per submitted request, in the same order.

**Each entry carries its own `requestBatchId`, and those IDs may or may not match across the batch.** Within a single submission, entries scheduled for the same business day share a `requestBatchId`; entries scheduled for different days get distinct ones. Whether your batch lands on a single day or spills across multiple days depends on your account's daily call capacity and any work already pending from prior submissions on those days. Always read each entry's `requestBatchId` from the response. Don't assume they match.

```bash theme={null}
curl -X POST https://robodialer-service-api-9nc4t1p9.uc.gateway.dev/v1/requests \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "requests": [
      {
        "schemaId": "fWxzG4nqtpHsJxS5Lm3q",
        "inputs": { "payerName": "Sample Insurance Co", "memberId": "TEST123456789", "phoneNumber": "2125551234" },
        "internalId": "claim_001"
      },
      {
        "schemaId": "fWxzG4nqtpHsJxS5Lm3q",
        "inputs": { "payerName": "Sample Insurance Co", "memberId": "TEST987654321", "phoneNumber": "2125551234" },
        "internalId": "claim_002"
      }
    ]
  }'
```

### Batch response

A batch responds with one entry per request, in the same order as the input. Each entry is either a success body or the uniform `{error, message, [details]}` envelope:

```json theme={null}
{
  "requests": [
    {
      "requestId": "8bF7xK2mP9qR4sT6uV0w",
      "requestBatchId": "pH9kJ2lM4nB6vC8xZ7Qr",
      "internalId": "claim_001"
    },
    {
      "error": "INVALID_REQUEST",
      "message": "schemaId is required"
    }
  ]
}
```

The HTTP status code reflects the aggregate outcome:

| Status code | Meaning                                                                                                                                                                                                                                                                                                   |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`       | All entries in the batch were created successfully.                                                                                                                                                                                                                                                       |
| `207`       | Mixed: at least one entry succeeded and at least one failed with a **client-side (4xx)** error. Inspect each entry.                                                                                                                                                                                       |
| `400`       | Either the top-level request body was invalid (e.g., `requests` was empty), OR every entry in the batch failed with a client-side error.                                                                                                                                                                  |
| `500`       | An internal failure occurred during batch processing. Body is **either** the top-level `{error: "INTERNAL_ERROR", message: "..."}` envelope (the common case) **or** `{"requests": [...]}` with per-entry envelopes. Check for the `requests` key to tell them apart, and treat each entry independently. |

<Tip>
  Batches don't have to be all-or-nothing. We recommend treating each entry independently: successful entries are real, persisted requests, even if other entries in the same batch failed.
</Tip>

<Note>
  To list every request under a given `requestBatchId` later, for example to track progress on one day's slice of a large batch, use [`GET /v1/requests?requestBatchId=...`](/guides/reading-requests#by-batch). For a multi-day batch, iterate over the unique IDs returned in the create response.
</Note>

## Required fields

Every request body must include:

| Field      | Description                                                                                                                                                                                                                                                                                                                                           |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schemaId` | The [schema](/guides/concepts#schema) that defines the output fields. Discover yours via [`GET /v1/schemas`](/guides/schemas).                                                                                                                                                                                                                        |
| `inputs`   | A JSON object of `{ key: value }` pairs. **All values must be strings.** The keys are schema-specific: discover required and optional ones via [`GET /v1/schemas/{schemaId}/required-inputs`](/guides/schemas#look-up-required-and-optional-inputs). Typical examples include `payerName`, `memberId`, `phoneNumber`, `providerNpi`, `dateOfService`. |

`requestType` is **server-derived** from the schema: you'll see the canonical value on the `RequestResponse` returned by `GET /v1/requests/{requestId}`. You can still send a `requestType` field on the POST body if existing client code does; it's silently ignored.

Required `inputs.{key}` fields are validated when the request is created: if any are missing or fail format checks, the request is rejected with HTTP 400 and the uniform `INVALID_INPUTS` error envelope:

```json theme={null}
{
  "error": "INVALID_INPUTS",
  "message": "Required inputs are missing or invalid.",
  "details": {
    "missingInputs": ["memberId", "providerNpi"],
    "invalidInputs": { "dateOfService": "must be YYYY-MM-DD" }
  }
}
```

`details` is only present on this `INVALID_INPUTS` path; other 400 errors carry just `error` + `message`. Discover the required `inputs` per schema with [`GET /v1/schemas/{schemaId}/required-inputs`](/api-reference/schemas/required-inputs-for-a-schema), or ask your account team. See the [API Reference for `POST /v1/requests`](/api-reference/requests/create-a-request) for the full request body schema.

<Note>
  If your account is enabled for [**per-payer required inputs**](/guides/payer-required-inputs) (opt-in: ask your account team), some payers require additional inputs beyond the schema's fields, depending on `payerName`. A missing one is reported here in `details.missingInputs` like any other. Add it and retry.
</Note>

## Optional fields

| Field                  | Description                                                                                                                                                                                                                                                                                                                                                                                                          |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `webhookUrl`           | Per-request [webhook](/guides/webhooks) URL override. Wins over the account default for this one request.                                                                                                                                                                                                                                                                                                            |
| `useMatchedPayerPhone` | Boolean (default `false`). When `true` and [payer phone number lookup](/guides/payer-resolution) is enabled, dial SuperDial's matched payer number even if you supplied `phoneNumber`, falling back to your number when no match is found. Ignored when lookup is off. Goes here at the top level of the request, not inside `inputs` — see [where to put the flag](/guides/payer-resolution#where-to-put-the-flag). |
| `internalId`           | **Optional** correlation ID, and also the request's idempotency key. Echoed on all reads and webhooks. If you supply one it must be **unique per request** (see below). If you omit it, SuperDial generates one for you.                                                                                                                                                                                             |
| `internalTag`          | Opaque tag echoed on reads and webhooks (e.g. `"march-batch"`).                                                                                                                                                                                                                                                                                                                                                      |
| `aiOnly`               | Boolean. When `true`, any phone call this request makes is handled only by our automated voice agent, never a human agent. Leaving it `false` adds no restriction: it doesn't force human-agent handling. Defaults to `false`.                                                                                                                                                                                       |
| `onshoreOnly`          | Boolean. When `true`, all phone handling for this request is restricted to US-based agents. Leaving it `false` adds no restriction. Defaults to `false`.                                                                                                                                                                                                                                                             |

## Correlation and idempotency

`internalId` is **optional** and serves two purposes:

1. **Correlation**: when you supply one, it's echoed back on every read and every webhook for that request, so you can tie SuperDial results to your own records.
2. **Idempotency**: `internalId` *is* the idempotency key. A `POST /v1/requests` that reuses an `internalId` you've sent before does **not** create a new request; it returns the originally created `requestId` (and the original `payerLookup`, if any).

<Warning>
  Because `internalId` is the idempotency key, **a new `internalId` must be different on every distinct request you want to place.** Reusing one is not an error and produces no warning: it silently returns the original request instead of starting new work. Use a value that's naturally unique per request (a UUID, or your own primary key), not a constant or a reused batch label.
</Warning>

**You don't have to send one.** `internalId` is optional. If you omit it, SuperDial generates and maintains its own unique idempotency key server-side, so retries are still de-duplicated for you. Supply your own only when you want to correlate results back to records on your end; otherwise leave it off and let SuperDial manage it.

```json theme={null}
// First call
POST /v1/requests  { "internalId": "claim_001", ... }
→ 200  { "requestId": "8bF7xK2mP9qR4sT6uV0w", ... }

// Retry with the SAME internalId: no new request is created
POST /v1/requests  { "internalId": "claim_001", ... }
→ 200  { "requestId": "8bF7xK2mP9qR4sT6uV0w", ... }   // same ID, no duplicate

// Omit internalId entirely: SuperDial generates its own key
POST /v1/requests  { ... }            // no internalId
→ 200  { "requestId": "aC3hN5jD8eL1fM2gK6Yo", ... }   // new request every call
```

`internalId`-based idempotency applies on a **per-request** basis. In batch submissions, each entry with an `internalId` is independently deduped: a duplicate entry returns the previously created `requestId` and `requestBatchId` rather than creating a new request. There is no batch-level idempotency key, only per-entry.

## What happens after creation

The request immediately begins running through one or more [modalities](/guides/concepts#modality) (electronic systems and/or phone calls). Completion time varies: electronic-only requests can finish quickly; phone-backed requests depend on payer responsiveness and hold times.

Configure a [webhook](/guides/webhooks) to be notified the moment a request finishes, then call [`GET /v1/requests/{requestId}`](/guides/reading-requests#single-request) to fetch the full result.

## Error handling

Every non-2xx response from `/v1/requests` returns the same envelope:

```json theme={null}
{
  "error": "MACHINE_CODE",
  "message": "Human-readable description.",
  "details": { /* optional, only on INVALID_INPUTS */ }
}
```

Switch on `error` for programmatic dispatch; surface `message` to humans.

| HTTP code | `error` code           | Meaning                                                                                                                                                                                                                                                                                                                                                                       | What to do                                                                                             |
| --------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `400`     | `INVALID_REQUEST`      | Body wasn't a JSON object, a required field is missing (`schemaId`, `inputs`), the batch `requests` array is empty, or scheduling capacity is exceeded. The validator's specific reason is in `message`.                                                                                                                                                                      | Fix the body and retry                                                                                 |
| `400`     | `INVALID_INPUTS`       | Input validation failed. `details.missingInputs` lists field names that weren't supplied; `details.invalidInputs` maps field name to the reason it was rejected. With [per-payer required inputs](/guides/payer-required-inputs) enabled, some payers add inputs that can appear here too.                                                                                    | Fix the inputs and retry                                                                               |
| `400`     | `PAYER_NOT_FOUND`      | Payer phone number lookup path: the supplied `payerName` did not match any known payer. Only fires when [lookup is enabled for your account](/guides/payer-resolution), `payerName` is set, and `phoneNumber` is not.                                                                                                                                                         | Confirm spelling, try a more canonical payer name, or supply `phoneNumber` directly to skip the lookup |
| `401`     | (gateway envelope)     | Bearer token missing, malformed, or expired. Returns the gateway's `{code, message}` shape, not the envelope above.                                                                                                                                                                                                                                                           | Fetch a fresh token via `GET /v1/auth`                                                                 |
| `404`     | `SCHEMA_NOT_FOUND`     | `schemaId` doesn't exist for your account, or has been retired. On a batch, schema-not-found surfaces per-entry as `INVALID_REQUEST` with `"Schema not found"` in `message`. Read `message` to distinguish 4xx causes when an entry has no status code.                                                                                                                       | Discover valid `schemaId` values with [`GET /v1/schemas`](/api-reference/schemas/list-schemas)         |
| `404`     | `ACCOUNT_NOT_FOUND`    | The API key resolves to an account that no longer exists. Rare: wrong-API-key cases hit `INVALID_API_KEY` at `/v1/auth` first.                                                                                                                                                                                                                                                | Contact support                                                                                        |
| `500`     | `INTERNAL_ERROR`       | Unexpected server failure. **Single-item POST:** body is the envelope above. **Batch:** body can take either shape: the top-level `INTERNAL_ERROR` envelope (most common) or `{"requests": [...]}` with per-entry envelopes (server-error entries surface here as `INVALID_REQUEST` rather than `INTERNAL_ERROR`). Check for the `requests` key to tell the two shapes apart. | Retry once; escalate if persistent                                                                     |
| `500`     | `PAYER_LOOKUP_FAILURE` | Payer phone number lookup path: the supplied `payerName` matched a payer but our database has no phone number on file, or the lookup hit a transient infra failure. Only fires when [lookup is enabled for your account](/guides/payer-resolution).                                                                                                                           | Retry with backoff, or supply `phoneNumber` directly to skip the lookup                                |

For batch submissions, **each failed entry inside the `requests` array carries the same envelope shape** as a top-level error, so you can use the same error handler everywhere.
