# Resumable Line Provisioning: Save the Assistant ID Before the SIP Call

> A line create command for AI phone receptionists that resumes each step from what the client file already proves, with one critical ordering fix: persist the assistant ID before the SIP create.

- Author: Dil Ahdan (https://dil.dev/)
- Published: 2026-11-03
- Updated: 2026-11-03
- Tags: voice-agents, provisioning, vapi, automation
- Canonical: https://dil.dev/notes/resumable-line-provisioning/

## Key takeaways

- Save the assistant ID to the client file before attempting the SIP create, or a re-run makes a duplicate assistant.
- VAPI refuses assistant names over 40 characters: fall back to the short name, then cut.
- VAPI refuses SIP usernames under 8 characters: enforce a minimum before the API call, not after.
- A store read outage on resume is a warning, not a hard stop: the re-run finishes cleanly.
- --json without --yes refuses instead of prompting, so non-interactive runs stay predictable.

The key insight from building this provisioning flow is simple: the order in which you persist state matters more than the order in which you make API calls. Get that wrong and a retry creates duplicate resources. Get it right and the whole flow becomes safely resumable from any failure point.

## What the Command Actually Does

`line create` runs six ordered steps for each new AI phone receptionist line.

- **Buy the number.** Acquires the phone number from the carrier.
- **Write the client file.** Creates `clients/<slug>.json` with everything the subsequent steps need.
- **Set up the sheet.** Provisions the associated data sheet for the line.
- **Provision the assistant.** Creates the VAPI assistant and persists its ID.
- **Archive folder and plan or apply.** Creates the archive folder and applies the line plan.
- **Brand registration.** Registers the brand for the line.

Each step reads `clients/<slug>.json` before doing anything. If the file already proves the step is done, the step is skipped. That is the entire resumability mechanism. There is no separate resume flag or checkpoint store: the client file is the checkpoint.

Steps are also mirrored to the `bbl-ops` lines table, so the ops view stays consistent with what the file says.

## The Ordering Fix That Matters

Before the fix, the sequence inside the provision step was:

1. Create the VAPI assistant.
2. Create the SIP number.
3. Write both IDs to the client file.

If the SIP call failed, the client file had neither ID. A re-run would create a second assistant, then try the SIP call again. You would end up with two assistants and one SIP number, and no clean way to know which assistant was intended.

The fix is to write the assistant ID to the client file immediately after the assistant is created, before the SIP call is attempted. The field is stored as `vapi.sipPending` to signal that the SIP step has not completed yet. On a re-run, the command sees the assistant ID already present, skips the assistant create, and only retries the SIP number. One assistant, one SIP number, regardless of how many times you re-run.

This is the same principle that applies to any multi-step API flow: persist what you have before you ask for what you need next.

## Two Provider Limits Found Live on 15 October

Both of these were discovered by hitting the API and reading the refusal, not from documentation.

**Assistant name length.** VAPI refused a 45-character assistant name. The limit is 40 characters. The fallback order is now:

- Try the full configured name.
- If over 40 characters, try the short name.
- If the short name is still over 40 characters, cut to 40.

This means the name in VAPI may not match the name in the client file exactly, but it will always be accepted.

**SIP username length.** VAPI refused a SIP username shorter than 8 characters. The command now validates the length before making the API call, so the error surfaces with a clear message rather than a raw API refusal mid-run.

Both limits are now enforced client-side before any network call, so they fail fast with a readable error rather than partway through a provisioning sequence.

## Store Outage on Resume

There is a third failure mode worth knowing about. If the store is unavailable when the command tries to read the resume state, earlier versions would hard-stop. The current behaviour is to log a warning and continue. A subsequent re-run reads the state correctly once the store is back, and finishes any remaining steps.

This matters in practice because a transient outage during a resume read should not require manual intervention. The client file is the source of truth: as long as that is intact, the run can always be recovered.

## Non-Interactive Runs

`--json` without `--yes` refuses rather than prompts. This is deliberate. A provisioning job running in a console or through the `bbl-ops` MCP server should never hang on an unexpected confirmation. If the command needs a decision it cannot make automatically, it should fail loudly so a human can look at it, not wait silently for input that will never come.

Two lines were created on 15 October using this flow: one through the `bbl-ops` MCP server and one through a console job. Both completed without manual intervention after the fixes were in place.

## How This Connects to Broader Agent Reliability

The pattern here is not specific to phone line provisioning. Any agent or automation that calls multiple external APIs in sequence needs the same discipline: write state before you make the next call, not after both calls succeed. The [idle watchdog and thinking model timeouts](/notes/idle-watchdog-killed-thinking-models/) note covers a related failure mode where an agent loop stalls mid-execution. The resumability fix here is the provisioning equivalent: make re-entry safe so a stall is recoverable.

If you are building something similar, the client file approach is worth considering over a dedicated checkpoint store. It is readable, diffable, and already exists for other reasons. The cost is that you need to be disciplined about what you write to it and when.

## What I'd do differently

- **Validate provider limits before the first real run, not after.** Both the name length and the SIP username minimum were found by hitting the API. A short validation step at the start of `line create` that checks these constraints against the config would surface them before any resources are created.
- **Make `vapi.sipPending` more explicit in the client file schema.** Right now it is a field name that implies meaning. A separate `provisioningState` object with named stages would make the resume logic easier to read and easier to extend when new steps are added.
- **Log the skipped steps explicitly.** When a re-run skips a step because the client file proves it is done, that should appear in the output. Silent skips make it harder to confirm that a resume went the way you expected.

## Quick answers

**What order do the provisioning steps run in?**

Buy the number, write the client file, set up the sheet, provision the assistant, create the archive folder and apply the plan, then register the brand. Each step checks the client file before doing anything, so re-runs skip what is already done.

**Why does saving the assistant ID before the SIP create matter?**

If the SIP call fails and you re-run, the command sees no assistant ID and creates a second one. Persisting the ID as vapi.sipPending immediately after the assistant is created means a re-run only retries the SIP number, leaving one assistant in place.

**What happens if the store is unavailable when the command tries to read the resume state?**

The outage is logged as a warning and the run continues. A subsequent re-run reads the state correctly and finishes any remaining steps.

**How does the name truncation work for VAPI assistants?**

The command tries the full name first. If that exceeds 40 characters it falls back to the configured short name. If the short name is still too long it cuts to 40 characters. A 45-character name was refused live on 15 October.

**What is the minimum SIP username length VAPI enforces?**

VAPI refused a username shorter than 8 characters in a live call on 15 October. The command now validates the length before making the API call.

**How does the command behave in non-interactive environments?**

Passing --json without --yes causes the command to refuse rather than prompt. This keeps automated runs predictable and prevents a provisioning job from hanging on an unexpected confirmation.
