SiteCallDevelopers

Routing plans

What happens when the phone rings.

A routing plan is a small graph of steps: an entry step and a map of named steps, each leading to the next by name. Every path must end in voicemail or a hangup: a plan with a loop is refused, and a menu repeats only through its own retries.

A plan

business hours, a queue, voicemail

{
  "entry": "hours",
  "steps": {
    "hours": { "type": "hours", "scheduleId": "<schedule id>", "open": "sales", "closed": "vm" },
    "sales": { "type": "queue", "queueId": "<queue id>", "maxWaitSecs": 300,
               "overflow": "vm", "offerCallback": false },
    "vm": { "type": "voicemail", "greeting": { "tts": "Thanks for calling Acme. Leave a message." } }
  },
  "recording": { "enabled": true, "disclosure": true }
}

Steps

TypeWhat it does
hoursBranch on a schedule: open or closed (scheduleId).
menuPlay a prompt and collect one key; each key leads to a step, noInput after maxRetries.
ringRing members, application users or cell numbers, simultaneous or sequential; next on no answer.
queueWait in a queue for the next agent; overflow after maxWaitSecs, optional callback offer.
ai_answerHand the caller to an AI agent (provider, agentId); onCap when the AI-minute cap is reached, next when it cannot.
forwardDial an external number; next on no answer.
voicemailTake a message (greeting text or audio, or the default).
hangupEnd the call, optionally after a message.

Versions, publishing and numbers

  1. POST /api/v1/routing-plans/validate checks a definition against the workspace (its members, users, queues, schedules, AI agents and destination policy) without saving it, and answers { valid, issues }.
  2. POST /api/v1/routing-plans saves the plan and its first version, a draft unless publish: true. Every PATCH with a definition saves a new immutable version.
  3. POST /api/v1/routing-plans/{id}/publish makes a version (default the newest) the one new calls run, after checking it again. Calls already in progress finish on the version they started with.
  4. POST /api/v1/routing-plans/{id}/assign { phoneNumberId } routes a number with the plan (a 409 not_published until a version is published). The number's calls must be on SiteCall's voice lane (see Numbers).

with @sitecall/cli

const check = await sitecall.routingPlans.validate({ definition: plan });
if (!check.valid) console.log(check.issues); // [{ stepId, code, message }]

const saved = await sitecall.routingPlans.create({ name: "Main line", definition: plan, publish: true });
await sitecall.routingPlans.assign(saved.id, { phoneNumberId });

A save or publish of an invalid plan is a 422 invalid_plan; its error envelope carries the same issues. Archiving a plan or deleting a queue or schedule a plan still uses is a 409 in_use.

The plan's JSON Schema

Generated from the validator the engine and the API use.

Routing plan JSON Schema
{
  "type": "object",
  "properties": {
    "entry": {
      "type": "string"
    },
    "steps": {
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "hours"
              },
              "scheduleId": {
                "type": "string"
              },
              "open": {
                "type": "string"
              },
              "closed": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "scheduleId",
              "open",
              "closed"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "menu"
              },
              "prompt": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "tts": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "tts"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "audioUrl": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "audioUrl"
                    ]
                  }
                ]
              },
              "options": {
                "type": "object",
                "propertyNames": {
                  "type": "string",
                  "enum": [
                    "0",
                    "1",
                    "2",
                    "3",
                    "4",
                    "5",
                    "6",
                    "7",
                    "8",
                    "9",
                    "*",
                    "#"
                  ]
                },
                "additionalProperties": {
                  "type": "string"
                }
              },
              "noInput": {
                "type": "string"
              },
              "timeoutSecs": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "maxRetries": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "type",
              "prompt",
              "options",
              "noInput",
              "timeoutSecs",
              "maxRetries"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "ring"
              },
              "targets": {
                "type": "array",
                "items": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "memberId": {
                          "type": "string"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "ring",
                            "silent",
                            "muted"
                          ]
                        }
                      },
                      "required": [
                        "memberId"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "userId": {
                          "type": "string"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "ring",
                            "silent",
                            "muted"
                          ]
                        }
                      },
                      "required": [
                        "userId"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "cell": {
                          "type": "string"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "ring",
                            "silent",
                            "muted"
                          ]
                        }
                      },
                      "required": [
                        "cell"
                      ]
                    }
                  ]
                }
              },
              "strategy": {
                "type": "string",
                "enum": [
                  "simultaneous",
                  "sequential"
                ]
              },
              "timeoutSecs": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "next": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "targets",
              "strategy",
              "timeoutSecs",
              "next"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "queue"
              },
              "queueId": {
                "type": "string"
              },
              "maxWaitSecs": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "overflow": {
                "type": "string"
              },
              "offerCallback": {
                "type": "boolean"
              }
            },
            "required": [
              "type",
              "queueId",
              "maxWaitSecs",
              "overflow",
              "offerCallback"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "ai_answer"
              },
              "provider": {
                "type": "string",
                "enum": [
                  "sitecall",
                  "gemini",
                  "elevenlabs"
                ]
              },
              "agentId": {
                "type": "string"
              },
              "onCap": {
                "type": "string"
              },
              "next": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "provider",
              "agentId",
              "onCap",
              "next"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "forward"
              },
              "to": {
                "type": "string"
              },
              "timeoutSecs": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "next": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "to",
              "timeoutSecs",
              "next"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "split"
              },
              "percentToA": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "a": {
                "type": "string"
              },
              "b": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "percentToA",
              "a",
              "b"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "voicemail"
              },
              "greeting": {
                "anyOf": [
                  {
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "tts": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "tts"
                        ]
                      },
                      {
                        "type": "object",
                        "properties": {
                          "audioUrl": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "audioUrl"
                        ]
                      }
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "type",
              "greeting"
            ]
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "hangup"
              },
              "message": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "type",
              "message"
            ]
          }
        ]
      }
    },
    "recording": {
      "type": "object",
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "disclosure": {
          "type": "boolean"
        }
      },
      "required": [
        "enabled",
        "disclosure"
      ]
    }
  },
  "required": [
    "entry",
    "steps"
  ]
}