> ## 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.

# Provision Orbit resources with Terraform

> Configure the Orbit Terraform provider, manage webhook endpoints, messaging services, numbers, subaccounts, campaigns, and agents as code, and import existing resources into state.

# Provision Orbit resources with Terraform

The Orbit Terraform provider lets platform teams provision and version Orbit
CPaaS resources as code in CI — the same way they manage the rest of their
cloud estate. It is a thin wrapper over the official [Orbit Go SDK](/sdks/go):
the provider configures one SDK client, and each resource maps Terraform
plan/state onto the Orbit REST API through it.

<Note>
  **Early access.** The provider's source lives in the Orbit monorepo. As with
  the Go SDK, a `github.com/devotel/terraform-provider-orbit` release and a
  registry publish are still pending, so `terraform init` cannot pull the
  released binary yet. Build from source until the registry listing ships; the
  `replace` directive in the provider's `go.mod` builds it against the in-repo
  SDK (run `go mod tidy` once to populate `go.sum`).
</Note>

## 1. Configure the provider

The provider needs an API key — a `dv_live_sk_…` key minted in the dashboard
under **Settings → API Keys**. Supply it either inline or through the
environment; the argument is marked sensitive, so it never lands in the
Terraform console output or state diagnostics.

| Argument   | Env var          | Required | Description                                         |
| ---------- | ---------------- | -------- | --------------------------------------------------- |
| `api_key`  | `ORBIT_API_KEY`  | yes      | Orbit API key (`dv_live_sk_…`). Marked sensitive.   |
| `base_url` | `ORBIT_BASE_URL` | no       | Override the API base URL (defaults to production). |

```hcl theme={null}
terraform {
  required_providers {
    orbit = {
      source = "registry.terraform.io/devotel/orbit"
    }
  }
}

provider "orbit" {
  # api_key may also be supplied via the ORBIT_API_KEY environment variable.
  api_key = var.orbit_api_key
}
```

In CI, keep the key out of config entirely — set `ORBIT_API_KEY` in the
pipeline's secret store and drop the `api_key` argument from the provider
block.

## 2. Resources

Six resources cover the Terraform-manageable surface. Every one supports
`terraform import <addr> <id>`.

| Resource                  | API routes                                              |
| ------------------------- | ------------------------------------------------------- |
| `orbit_webhook_endpoint`  | `POST/GET/PUT/DELETE /webhooks[/:id]`                   |
| `orbit_messaging_service` | `POST/GET/PATCH/DELETE /messaging/services[/:id]`       |
| `orbit_number`            | `POST /numbers/purchase`, `GET/PUT/DELETE /numbers/:id` |
| `orbit_subaccount`        | `POST/GET/PUT/DELETE /subaccounts[/:id]`                |
| `orbit_campaign`          | `POST/GET/PUT/DELETE /campaigns[/:id]`                  |
| `orbit_agent`             | `POST/GET/PUT/DELETE /agents[/:id]`                     |

Every resource follows the same template: the provider configures one SDK
client, and each resource maps Terraform plan/state onto the REST API through
it.

### `orbit_webhook_endpoint`

| Attribute         | Type         | Notes                                                                |
| ----------------- | ------------ | -------------------------------------------------------------------- |
| `id`              | string (out) | Server-assigned identifier.                                          |
| `url`             | string       | HTTPS delivery URL. Must not resolve to a private/loopback host.     |
| `events`          | list(string) | Event types to subscribe to. At least one.                           |
| `active`          | bool         | Defaults to `true`.                                                  |
| `description`     | string       | Optional, max 255 chars.                                             |
| `timeout_seconds` | number       | Optional per-endpoint delivery timeout (1..30s). Defaults to 30.     |
| `secret`          | string (in)  | Optional signing secret (min 16 chars). Write-only; never read back. |

### `orbit_number`

Purchases a number from inventory (`POST /numbers/purchase`) and configures
its inbound routing. `phone_number` and `country_code` force replacement;
`label`, `status`, `capabilities`, `forwarding_number`, `compliance_profile_id`
and `tags` are updated in place. Outbound termination is owned by the platform
softswitch and is intentionally not configurable here.

### `orbit_messaging_service`

Bundles a sender pool, opt-out list and delivery/throughput configuration for
outbound SMS/MMS. `mps_cap` sets a messages-per-second throughput cap (omit
for unbounded). Nullable attributes removed from config are cleared on the
next apply.

### `orbit_subaccount`

A child organization under the authenticated tenant with its own team-member
ceiling, rate limit and opening credit balance. `slug` and
`initial_credits_cents` force replacement. `api_key` is minted once at
creation and exposed as a sensitive computed attribute — it is never read
back.

### `orbit_campaign`

A marketing campaign (blast / drip / journey). Campaigns are created in
`draft`; sending is a runtime action and is not part of the declarative
lifecycle.

### `orbit_agent`

An AI agent — a named conversational assistant with a system prompt, model
and generation settings. The resource manages the agent's portable
configuration; the tool registry, knowledge-base bindings and nested runtime
`config` are managed through their own surfaces and are intentionally out of
scope. `type`, `status`, `model`, `temperature` and `max_tokens` are
server-defaulted when omitted.

## 3. Worked example: messaging service + webhook endpoint

This configuration purchases a number, wires a messaging service to it, and
subscribes a webhook endpoint to delivery receipts:

```hcl theme={null}
terraform {
  required_providers {
    orbit = {
      source = "registry.terraform.io/devotel/orbit"
    }
  }
}

provider "orbit" {
  # ORBIT_API_KEY picked up from the environment.
}

resource "orbit_number" "support" {
  phone_number      = "+14155552671"
  country_code      = "US"
  label             = "Support line"
  forwarding_number = "+14155550000"
  tags              = ["support", "us"]
}

resource "orbit_messaging_service" "transactional" {
  label               = "Transactional"
  sender_pool_id      = orbit_number.support.id
  inbound_webhook_url = "https://hooks.example.com/inbound"
  sticky_sender       = true
  mps_cap             = 100
}

resource "orbit_webhook_endpoint" "delivery_status" {
  url             = "https://hooks.example.com/orbit"
  events          = ["message.delivered", "message.failed"]
  active          = true
  description     = "Production delivery receipts"
  timeout_seconds = 10
  secret          = var.orbit_webhook_secret
}
```

Plan and apply:

```sh theme={null}
terraform init
terraform plan
terraform apply
```

`apply` prints the server-assigned ids. Bring pre-existing resources under
management with `terraform import`:

```sh theme={null}
terraform import orbit_webhook_endpoint.delivery_status whk_123
terraform import orbit_agent.support agt_123
terraform import orbit_number.support num_123
```

After each import, run `terraform plan` — it diffs the imported state against
your config, and any attribute the config omits shows as drift to reconcile.

## 4. Drift and import patterns

* **Import-then-diff.** `terraform import` pulls the resource's current
  server state into the state file; it does not write config for you. After
  import, copy the attribute values from `terraform state show <addr>` into
  your `.tf` files so the next `plan` is empty.
* **Replacement-forcing attributes.** Changing a force-replacement attribute
  (`orbit_number.phone_number`, `orbit_subaccount.slug`,
  `orbit_subaccount.initial_credits_cents`) destroys and recreates the
  resource on the next apply. Review the plan before approving.
* **Clearing nullable attributes.** On `orbit_messaging_service`, removing a
  nullable attribute from config clears it server-side on the next apply —
  the dashboard and Terraform stay in sync both directions.
* **Write-only secrets.** `orbit_webhook_endpoint.secret` and
  `orbit_subaccount.api_key` are never read back from the API, so Terraform
  cannot detect out-of-band changes to them. Rotate them by updating the
  variable and re-applying.
* **Detect out-of-band edits.** `terraform plan` with no local changes
  reports any attribute drifted since the last apply; run it on a schedule
  (or `terraform plan -refresh-only`) to catch dashboard-side edits.

## 5. What is intentionally not configurable

Outbound voice and SMS termination is owned by the platform softswitch — no
Terraform resource can set an outbound carrier, SIP trunk, or messaging
route, and none ever will. The same holds for the agent tool registry and
knowledge-base bindings (managed through their own surfaces) and for campaign
sending, which is a runtime action, not declarative configuration. Everything
else in the six resources above is supported.

## See also

* [Go SDK](/sdks/go) — the SDK the provider wraps
* [Work through the SDK catalog](/guides/developer-sdks-catalog)
* [Webhooks](/api-reference) — webhook endpoint API reference
* [First webhook quickstart](/guides/first-webhook-quickstart)
