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

# Terraform model — Orbit resources as infrastructure

> How the Devotel Orbit Terraform provider turns the REST API into a declarative surface — provider-as-SDK-wrapper, the six managed resources, the plan/state lifecycle, env-var key resolution, drift semantics, and the pre-registry source build

# Terraform model — Orbit resources as infrastructure

Terraform's model is simple: declare the desired state of a resource, and
the provider converges reality toward it. The Orbit Terraform provider
applies that model to the CPaaS surface — webhook endpoints, messaging
services, numbers, subaccounts, campaigns, and AI agents become HCL
resources you version, review, and apply in CI. This page explains the
model: what the provider is, which API surface it governs, and how
plan/state semantics map onto the REST API. For copy-pasteable HCL and
per-resource attributes, use the
[Terraform provider guide](/guides/terraform-provider).

## What "Orbit as IaC" means

The provider is a thin wrapper over the official
[Orbit Go SDK](/sdks/go). Its entire job is lifecycle framing:

1. **One client.** At provider-configure time it builds a single SDK
   client — one API key, one optional base-URL override — and hands that
   client to every resource.
2. **One schema per resource.** Each resource declares a Terraform
   schema (required, optional, computed attributes) whose fields mirror
   the OpenAPI shape of the corresponding REST resource one-to-one.
3. **Verb mapping.** Terraform's four lifecycle hooks (Create, Read,
   Update, Delete) call the corresponding HTTP verb on the API path for
   that resource. No Terraform-specific magic exists anywhere — the
   provider sends exactly the requests you would send with the SDK by
   hand.

That thinness is the design. The authoritative contract stays the
published REST API and its OpenAPI document; Terraform is one more
client of that contract, so behaviour you verify with `curl` holds under
`terraform apply`, and every new API field the REST surface ships is a
candidate attribute rather than a new integration to build.

## The resource inventory

Six resources cover the manage-as-code surface:

| Resource | Governs |
| - | - |
| `orbit_webhook_endpoint` | Event-delivery subscriptions (URL, event list, signing secret) |
| `orbit_messaging_service` | Outbound SMS/MMS configuration — sender pool, opt-out list, throughput cap |
| `orbit_number` | Numbers purchased from inventory + inbound routing settings |
| `orbit_subaccount` | Child organizations under your tenant with their own limits |
| `orbit_campaign` | Marketing campaigns (blast / drip / journey), created in `draft` |
| `orbit_agent` | AI agents — portable model/system-prompt configuration |

What is deliberately excluded, and why:

* **Outbound voice and SMS termination.** Carrier routing on Orbit is
  owned by the platform softswitch — no Terraform resource sets an
  outbound carrier, SIP trunk, or messaging route, and none ever will.
  `orbit_number` manages *inbound* routing only.
* **Runtime actions.** Campaign sending, agent invocation, live
  messaging — anything imperative — stays out of the declarative
  lifecycle. Terraform manages durable configuration, not runtime
  triggers.
* **Nested agent internals.** An agent's tool registry and
  knowledge-base bindings have their own surfaces; `orbit_agent` manages
  the portable agent config (name, prompt, model, generation settings)
  and deliberately stops there.
* **Read-most surfaces.** Dashboard-only conveniences with no stable
  idempotent mutation surface (onboarding wizards, playground state)
  are not Terraform material.

The exclusion list is the invariant that keeps the provider honest: if a
resource cannot be expressed as create/read/update/delete against the
REST contract, it does not get a Terraform resource.

## The plan/state lifecycle

Every `terraform plan` is a diff between three things: your HCL, the
last-known state, and a fresh read of the server. The mapping to the API
is mechanical:

| Terraform operation | API request |
| - | - |
| `Create` (apply new) | `POST /collection` (numbers: `POST /numbers/purchase`) |
| `Read` (refresh on plan) | `GET /collection/:id` |
| `Update` (apply change) | `PUT` or `PATCH /collection/:id` |
| `Delete` (apply removal) | `DELETE /collection/:id` |
| `Import` | `GET /collection/:id`, captured into state |

Two mechanical rules fall out of this mapping:

* **Computed fields converge to the server.** Fields marked computed
  (server-assigned ids, server-defaulted flags) are re-read on every
  refresh, so the state file always carries what the API returns — not
  what HCL last said.
* **Removed attributes are cleared explicitly.** On update, attributes
  the server treats as nullable are sent as explicit `null` when HCL
  omits them. Without that rule, deleting an attribute from config would
  silently leave the old value server-side. With it, the next apply
  brings the server back to exactly what config declares.

`terraform import <addr> <id>` recovers drift at the whole-resource
level: it fetches the resource's current server state and captures it as
the state baseline. Import does not write HCL — run
`terraform state show <addr>`, copy the values into config, and the next
plan goes empty.

## API key resolution — argument vs environment

The provider accepts two configuration inputs, and resolution order is
argument-first:

| Argument | Env var | Resolution |
| - | - | - |
| `api_key` | `ORBIT_API_KEY` | Explicit `api_key` wins; env is the fallback |
| `base_url` | `ORBIT_BASE_URL` | Explicit `base_url` wins; env is fallback |

`api_key` is a sensitive attribute — Terraform masks it from console
output and state diagnostics. The CI-friendly pattern is to omit the
argument entirely and set `ORBIT_API_KEY` in the pipeline's secret
store, so no key material sits in version control. `base_url` exists to
point the provider at a staging Orbit deployment; production needs
neither.

## Drift-reconciliation semantics

Drift is any mutation made out-of-band — a dashboard edit, an SDK
script, a support-console change. Reconciliation happens on refresh:

1. **Planned refresh.** Every `terraform plan` re-reads each managed
   resource via `GET`, so a dashboard-side edit surfaces as a diff
   before you apply anything.
2. **Config wins.** When you apply, the diff is resolved in the
   direction of HCL — the provider PATCHes the drifted attributes back
   to declared values.
3. **Deleted server-side → removed from state.** If a refresh gets a
   `404`, the resource is dropped from state and the next plan proposes
   recreating it — destroy-and-recreate is explicit in the plan output,
   never silent.
4. **Write-only fields cannot drift-check.** Secrets the API never
   returns for reads (webhook signing secrets, subaccount API keys) are
   write-only by design: Terraform cannot detect out-of-band rotation of
   them. Rotate by updating the variable and re-applying.
5. **Force-replacement attributes.** Where the API cannot mutate a
   field in place (a number's `phone_number`, a subaccount's `slug`),
   change means destroy + create. The plan names the replacement before
   approving.

Schedule `terraform plan` (or `terraform plan -refresh-only`) in CI to
turn drift from a surprise at apply time into a report.

## Before the registry release — build from source

The provider is early access. Until the registry listing ships, its
source of truth is the provider source tree, and `terraform init` cannot
pull a released binary. Locally:

```sh theme={null}
cd packages/terraform-provider-orbit
go mod tidy          # populate go.sum against the in-repo Go SDK
go build -o terraform-provider-orbit
```

Then install the binary into your Terraform plugin directory (or point
`dev_overrides` in your Terraform CLI config at the built path) so
`terraform init` resolves the local build. Once the
`registry.terraform.io/devotel/orbit` listing publishes, the same
config works with no source build.

## Where to go next

The [Terraform provider guide](/guides/terraform-provider) is the
operational companion to this page: full HCL examples, the
per-resource attribute tables, and the drift-and import patterns
walked end-to-end. This page explained the model; the guide shows the
config.

## See also

* [Provision Orbit resources with Terraform (guide)](/guides/terraform-provider)
* [Go SDK](/sdks/go) — the SDK the provider wraps
* [API keys, messaging, CCaaS & CDP](/guides/api-keys-messaging-ccaas-cdp)
* [Webhooks concept — delivery semantics](/concepts/webhook-delivery-semantics)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.