> For the complete documentation index, see [llms.txt](https://docs.firefly.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.firefly.ai/detailed-guides/bulk-onboarding/aws.md).

# AWS

**Scope: CLI and API only.** This guide covers the `aws` CLI and the Firefly API for onboarding AWS accounts without the console wizard. Firefly also ships a Terraform module for AWS onboarding, documented separately.

## When to use this

Use this guide to bulk onboard multiple AWS accounts, drive onboarding from a CI/CD pipeline, or give a security team a script to review before it runs. For a single account, the console wizard (**Settings > Integrations > Add New > AWS**) is faster. For an entire AWS Organization, use the StackSet section below rather than looping the single-account flow across every account.

## Prerequisites

* Admin access to the Firefly console (to create an API key pair)
* AWS CLI v2, configured with credentials for each target account
* IAM permissions to create CloudFormation stacks, IAM roles, and IAM policies
* `curl` and `jq`

## The two External ID models

AWS onboarding uses an External ID to secure the cross-account IAM role trust relationship. Which model applies depends on how you deploy:

|                       | Per-account                                                               | Organization-wide                       |
| --------------------- | ------------------------------------------------------------------------- | --------------------------------------- |
| External ID origin    | Generated by Firefly, one ID per registration call                        | One value reused across every account   |
| How you get it        | Returned by the registration call                                         | Provided by your Firefly representative |
| Deployment            | One stack per account, or a StackSet with per-account parameter overrides | A StackSet with OU targeting            |
| Reuse across accounts | No — each registration call issues a new ID                               | Yes, by design                          |

Pick one model and stick to it. Reusing a single External ID while also using the per-account registration call is not supported — either model works on its own, but mixing them does not.

## Flow — single account

1. Create an API key pair in Firefly
2. Authenticate and obtain a bearer token
3. Register the AWS account — this returns the External ID
4. Deploy the CloudFormation stack using that External ID
5. Verify

Steps 3 and 4 repeat per account. Steps 1 and 2 are done once. Registration must happen before the CloudFormation deploy — the registration call is what generates the External ID that the IAM role's trust policy needs.

## Step 1 — Create an API key pair

1. In the Firefly console, go to **Settings > Users**
2. Click **Create Key Pair**
3. Copy both the Access Key and Secret Key immediately — they are shown once
4. Store them in a secrets manager

These keys authenticate to your whole Firefly tenant, not to a single integration. Never commit them to version control.

## Step 2 — Authenticate

```shell
FIREFLY_API="https://prodapi.firefly.ai/api"

TOKEN=$(curl -sS -X POST "${FIREFLY_API}/account/access_keys/login" \
  -H "Content-Type: application/json" \
  -d "{\"accessKey\":\"${FIREFLY_ACCESS_KEY}\",\"secretKey\":\"${FIREFLY_SECRET_KEY}\"}" \
  | jq -r '.access_token // .accessToken')
```

Tokens default to 24-hour validity. For long-running jobs, re-authenticate rather than assuming the token survives.

## Step 3 — Register the account and capture the External ID

```shell
EXTERNAL_ID=$(curl -sS -X POST "${FIREFLY_API}/integrations/aws" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"accountNumber":"123456789012","nickname":"Production AWS"}' \
  | jq -r '.externalId')

echo "External ID: ${EXTERNAL_ID}"
```

| Field           | Type   | Description                                                |
| --------------- | ------ | ---------------------------------------------------------- |
| `accountNumber` | string | 12-digit AWS account ID, no spaces or dashes               |
| `nickname`      | string | Display name in the Firefly console, e.g. `Prod-US-East-1` |

This call queues the account for onboarding; Firefly then attempts to assume the role in the target account using this External ID, so the role must exist with the correct trust policy before validation succeeds.

Settings such as production flagging, event-driven regions, full-scan behavior, and IaC auto-discovery aren't part of this payload — configure those in the console after onboarding.

## Step 4 — Deploy the CloudFormation stack

```shell
aws cloudformation create-stack \
  --stack-name firefly-readonly \
  --template-url https://infralight-templates-public.s3.amazonaws.com/config_template.yml \
  --parameters ParameterKey=ExternalID,ParameterValue="${EXTERNAL_ID}" \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
  --region us-east-1

aws cloudformation wait stack-create-complete \
  --stack-name firefly-readonly --region us-east-1
```

The stack creates a cross-account IAM role with read-only (security audit) permissions, and optionally SNS notifications for event-driven tfstate scanning. The template is public and reviewable at the URL above — share it with security teams that ask.

By default the role is created as `firefly-caa-role`. If your organization requires a custom naming convention for IAM resources, contact your Firefly representative before setting `ResourceNamePrefix` to confirm it stays compatible with role validation.

## AWS Organizations — StackSet via CLI

Above roughly ten accounts, prefer a StackSet to looping the single-account flow. Which permission model you choose follows from which External ID model you're using.

### Option A — service-managed, OU targeting, one shared External ID

The simplest option to operate. Requires trusted access between CloudFormation and AWS Organizations, and must be run from the management or a delegated admin account. New accounts added to a targeted OU are onboarded automatically.

```shell
aws cloudformation create-stack-set \
  --stack-set-name firefly-readonly \
  --template-url https://infralight-templates-public.s3.amazonaws.com/config_template.yml \
  --capabilities CAPABILITY_NAMED_IAM \
  --permission-model SERVICE_MANAGED \
  --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false \
  --parameters ParameterKey=ExternalID,ParameterValue="${SHARED_EXTERNAL_ID}"

aws cloudformation create-stack-instances \
  --stack-set-name firefly-readonly \
  --deployment-targets OrganizationalUnitIds=ou-xxxx-aaaaaaaa,ou-xxxx-bbbbbbbb \
  --regions us-east-1 \
  --operation-preferences MaxConcurrentCount=10,FailureToleranceCount=10,ConcurrencyMode=SOFT_FAILURE_TOLERANCE
```

Contact your Firefly representative to obtain the shared External ID for this deployment model — it isn't the value returned by the per-account registration call in Step 3.

`SOFT_FAILURE_TOLERANCE` matters at scale: without it, a handful of accounts with restrictive SCPs will stall the whole rollout.

### Option B — self-managed, per-account External IDs

Use when each account needs its own External ID from Step 3. StackSet parameter overrides are applied per account, so the same StackSet serves all of them.

Requires `AWSCloudFormationStackSetAdministrationRole` in the admin account and `AWSCloudFormationStackSetExecutionRole` in each target account.

```shell
aws cloudformation create-stack-set \
  --stack-set-name firefly-readonly \
  --template-url https://infralight-templates-public.s3.amazonaws.com/config_template.yml \
  --capabilities CAPABILITY_NAMED_IAM \
  --permission-model SELF_MANAGED \
  --parameters ParameterKey=ExternalID,ParameterValue=placeholder

# per account, using the External ID returned by its own registration call
aws cloudformation create-stack-instances \
  --stack-set-name firefly-readonly \
  --accounts 123456789012 \
  --regions us-east-1 \
  --parameter-overrides ParameterKey=ExternalID,ParameterValue="${EXTERNAL_ID}" \
  --operation-preferences MaxConcurrentCount=5,FailureToleranceCount=5
```

Monitor a rollout with:

```shell
aws cloudformation list-stack-instances --stack-set-name firefly-readonly \
  --query "Summaries[].{Account:Account,Status:Status,Reason:StatusReason}" --output table
```

We recommend validating this approach on two accounts before rolling it out broadly.

## Step 5 — Verify

1. Firefly console > **Settings > Integrations > AWS**
2. Confirm the account appears and shows as connected
3. Open **Inventory** and filter by AWS — initial discovery takes 5–10 minutes

To force a rescan: on the integration menu, **Scan Assets** (cloud resources) or **Scan Stacks** (IaC state).

## Bulk onboarding script

For a set of standalone accounts. For an Organization, prefer the StackSet path above.

```shell
#!/usr/bin/env bash
set -euo pipefail

FIREFLY_API="https://prodapi.firefly.ai/api"
ACCESS_KEY="${FIREFLY_ACCESS_KEY:?set FIREFLY_ACCESS_KEY}"
SECRET_KEY="${FIREFLY_SECRET_KEY:?set FIREFLY_SECRET_KEY}"

# "account_id:nickname:aws_cli_profile"
ACCOUNTS=(
  "123456789012:Production:prod"
  "234567890123:Development:dev"
)

TOKEN=$(curl -sS -X POST "${FIREFLY_API}/account/access_keys/login" \
  -H "Content-Type: application/json" \
  -d "{\"accessKey\":\"${ACCESS_KEY}\",\"secretKey\":\"${SECRET_KEY}\"}" \
  | jq -r '.access_token // .accessToken')

if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
  echo "Authentication failed"; exit 1
fi

for entry in "${ACCOUNTS[@]}"; do
  IFS=':' read -r ACCOUNT_ID NICKNAME PROFILE <<< "$entry"
  echo "=== ${NICKNAME} (${ACCOUNT_ID}) ==="

  EXTERNAL_ID=$(curl -sS -X POST "${FIREFLY_API}/integrations/aws" \
    -H "Authorization: Bearer ${TOKEN}" \
    -H "Content-Type: application/json" \
    -d "{\"accountNumber\":\"${ACCOUNT_ID}\",\"nickname\":\"${NICKNAME}\"}" \
    | jq -r '.externalId')

  if [ -z "$EXTERNAL_ID" ] || [ "$EXTERNAL_ID" = "null" ]; then
    echo "  registration failed, skipping"; continue
  fi

  aws cloudformation create-stack \
    --stack-name firefly-readonly \
    --template-url https://infralight-templates-public.s3.amazonaws.com/config_template.yml \
    --parameters ParameterKey=ExternalID,ParameterValue="${EXTERNAL_ID}" \
    --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
    --region us-east-1 --profile "${PROFILE}"

  aws cloudformation wait stack-create-complete \
    --stack-name firefly-readonly --region us-east-1 --profile "${PROFILE}" \
    && echo "  done" || echo "  stack failed"
done
```

The token is fetched once outside the loop; the External ID is fetched fresh per account. If you extend this past a few dozen accounts, note the API rate limit of 500 requests per rolling minute per source IP.

## Troubleshooting

| Symptom                                             | Likely cause                                                                                                          |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Stack creation fails on IAM resources               | Missing `CAPABILITY_NAMED_IAM`, or the deploying principal cannot create roles                                        |
| Integration stays pending / "unable to assume role" | External ID mismatch between the registration call and the stack parameter, or the role name isn't `firefly-caa-role` |
| Registration returns 401                            | Token expired, or the bearer header is malformed                                                                      |
| No resources in Inventory after 15 min              | Check integration status in the console; confirm the role policy was created and not stripped by an SCP               |
| StackSet rollout stalls partway                     | Restrictive SCPs on some accounts; add `ConcurrencyMode=SOFT_FAILURE_TOLERANCE`                                       |
| StackSet instances fail immediately (self-managed)  | `AWSCloudFormationStackSetExecutionRole` missing in the target account                                                |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.firefly.ai/detailed-guides/bulk-onboarding/aws.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
