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

# Importing an Existing Workspace

> Adopt projects, models, keys, and other resources that already exist in the dashboard into Terraform, one at a time or the whole workspace at once.

Every resource the provider manages can be imported: a project, API key, model grant, budget, notifier, guardrail rule, routing rule, evaluator, or management key created in the dashboard can be adopted by Terraform without recreating it. This page covers importing a single resource, then generating a starting configuration for an entire workspace at once.

## Import a single resource

Add an `import` block naming the resource address and the id to import, then run `plan` with `-generate-config-out` to write a starting configuration:

```hcl imports.tf theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
  to = orq_project.production
  id = "019facd8-a60b-7a97-bd30-bf8e7280058a"
}
```

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
terraform plan -generate-config-out=generated.tf
```

Both tools print a warning that configuration generation is experimental. The output is still directly usable: review `generated.tf`, move its content into the file it belongs in, delete the `import` block, and run `apply`.

<Note>Multiple `import` blocks in one file generate the whole batch in a single `generated.tf`. Every id form the resource's own import documentation accepts (see each resource's page on the [Terraform Registry](https://registry.terraform.io/providers/orq-ai/orq/latest/docs)) works in an `import` block too.</Note>

## Import an entire workspace

For a first adoption, generating one `import` block per existing resource by hand does not scale. The script below discovers every resource in a workspace through the same API the dashboard uses, and prints one `import` block per resource to stdout.

```bash gen-imports.sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
#!/usr/bin/env bash
# Generate `import` blocks for every orq resource in a workspace.
# Requires ORQ_API_BASE_URL and ORQ_API_KEY (an ALL-mode management key).
#
# A resource type that errors (a transient outage, a permission gap) is
# SKIPPED with a warning on stderr rather than aborting the whole run --
# review stderr afterward for anything that needs a second pass.
set -uo pipefail
: "${ORQ_API_KEY:?ORQ_API_KEY is required}"
BASE="${ORQ_API_BASE_URL:-https://my.orq.ai}"

get() { curl -sf --retry 3 --retry-delay 1 "$BASE$1" -H "Authorization: Bearer $ORQ_API_KEY"; }

paginate() {
  local path="$1" cursor="" page
  while :; do
    if [ -n "$cursor" ]; then page=$(get "$path?limit=100&starting_after=$cursor") || return 1
    else page=$(get "$path?limit=100") || return 1; fi
    printf '%s\n' "$page"
    cursor=$(printf '%s' "$page" | python3 -c '
import sys, json
d = json.load(sys.stdin)
items = d["data"] if isinstance(d, dict) else d
if isinstance(d, dict) and d.get("has_more") and items:
    print(items[-1].get("_id") or items[-1].get("id"))
' 2>/dev/null || true)
    [ -n "$cursor" ] || break
  done
}

emit() { # emit <resource_type> <name> <id>
  python3 -c '
import re, os, sys
rtype, name, rid, seen_file = sys.argv[1:5]
seen = set(open(seen_file).read().split()) if os.path.exists(seen_file) else set()
s = re.sub(r"[^a-zA-Z0-9]+", "_", name or "unnamed").strip("_").lower() or "unnamed"
if not s[0].isalpha():
    s = "r_" + s
key, base, n = f"{rtype}.{s}", s, 2
while key in seen:
    s = f"{base}_{n}"; n += 1; key = f"{rtype}.{s}"
seen.add(key)
open(seen_file, "w").write("\n".join(seen))
print(f"import {{\n  to = {rtype}.{s}\n  id = \"{rid}\"\n}}\n")
' "$1" "$2" "$3" "$SEEN_FILE"
}

SEEN_FILE=$(mktemp)
trap 'rm -f "$SEEN_FILE"' EXIT

collect() {
  local rtype="$1" path="$2" prog="$3" pages
  if ! pages=$(paginate "$path"); then
    echo "warning: skipping $rtype ($path is unavailable)" >&2
    return
  fi
  printf '%s\n' "$pages" | python3 -c "
import sys, json
for line in sys.stdin:
    line = line.strip()
    if not line: continue
    try:
        d = json.loads(line)
    except json.JSONDecodeError:
        print('warning: skipping a malformed page from $path', file=sys.stderr)
        continue
    items = d['data'] if isinstance(d, dict) else d
    for item in items:
$prog
" | while IFS=$'\t' read -r name id; do emit "$rtype" "$name" "$id"; done
}

collect orq_project          /v2/projects         "        print(item['name'], item['project_id'], sep='\t')"
collect orq_api_key          /v2/api-keys         "        print(item['name'], item['id'], sep='\t')"
collect orq_budget           /v2/budgets          "        print(item.get('scope',{}).get('kind','budget'), item['budgetId'], sep='\t')"
collect orq_notifier         /v2/notifiers        "        print(item['display_name'], item['_id'], sep='\t')"
collect orq_guardrail_rule   /v2/guardrail-rules  "        print(item['display_name'], item['_id'], sep='\t')"
collect orq_routing_rule     /v2/routing-rules    "        print(item['display_name'], item['_id'], sep='\t')"
collect orq_management_key   /v2/management-keys  "        print(item['name'], item['management_key_id'], sep='\t')"
collect orq_evaluator        /v2/evaluators       "        print(item.get('key') or item['_id'], item['_id'], sep='\t')"

if models=$(get /v2/models); then
  printf '%s' "$models" | python3 -c '
import sys, json
for m in json.load(sys.stdin):
    if m.get("enabled"): print("orq_workspace_model", m["model_id"], m["id"], sep="\t")
    if m.get("owner") not in (None, "system"):
        provider = m.get("provider")
        if provider == "aws":
            print("orq_bedrock_model", m["display_name"], m["id"], sep="\t")
        elif provider == "openailike":
            print("orq_model", m["display_name"], m["id"], sep="\t")
        # else: unmanaged custom-model provider (azure, vertex, litellm...) -- skip.
' | while IFS=$'\t' read -r rtype name id; do emit "$rtype" "$name" "$id"; done
else
  echo "warning: skipping workspace_model/model/bedrock_model (/v2/models is unavailable)" >&2
fi

printf 'import {\n  to = orq_workspace_settings.this\n  id = "workspace"\n}\n'
```

Run it and generate configuration for the whole batch in one pass:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
export ORQ_API_BASE_URL="https://my.orq.ai"   # or the staging / on-prem URL
export ORQ_API_KEY="sk-orq-..."                # an ALL-mode management key

./gen-imports.sh > imports.tf
terraform plan -generate-config-out=generated.tf
terraform apply
```

Importing only ever reads: it does not create, modify, or delete anything server-side. It is safe to run against a live, in-use workspace. Once `generated.tf` looks right, delete `imports.tf` (or the individual blocks that succeeded) so a later `plan` does not keep re-evaluating them.

### Resources that need a manual pass

Config generation depends on the API returning enough information to reconstruct every attribute. A few attributes it cannot recover, verified against a live workspace:

<AccordionGroup>
  <Accordion title="Write-only credentials">
    `orq_model.api_key`, and `orq_bedrock_model.assume_role_arn` / `assume_role_external_id`, are never returned by the API after creation. The generated resource is missing them and fails to plan (`Missing Configuration for Required Attribute`) until they are added by hand, sourced from wherever the original credential is kept.
  </Accordion>

  <Accordion title="orq_workspace_model.sharing">
    `sharing` is required but populated only from a server read, which `-generate-config-out` cannot turn into an attribute value for a required nested block. Import the resource on its own first, then copy its actual sharing configuration from `terraform state show orq_workspace_model.<name>` into the generated block.
  </Accordion>

  <Accordion title="Evaluators and models the provider does not manage">
    `orq_evaluator` manages only `llm_eval` and `python_eval` evaluators; other types (`function_eval`, `ragas`, `json_schema`, `http_eval`, `typescript_eval`, `bedrock_eval`) are refused. `orq_model` manages only custom models created as `openai-like`; a custom model on another provider (Azure, Vertex, a LiteLLM import) has no matching resource yet. The script above skips these automatically.
  </Accordion>

  <Accordion title="A resource type that is temporarily unreachable">
    The script skips a resource type outright rather than aborting the run if its listing endpoint errors, and reports it on stderr. Re-run it later, or import that type individually with a single `import` block.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Supported resources" icon="list" href="/reference/terraform/resources">
    Every resource the provider manages, with links to the full reference.
  </Card>

  <Card title="Enterprise baseline" icon="shield-check" href="/reference/terraform/enterprise-baseline">
    A pre-written locked-down setup to compare an imported workspace against.
  </Card>
</CardGroup>
