Send predictions to Amazon S3
An Amazon S3 destination writes every prediction served to your account into a bucket you own, as gzipped JSON Lines. Your AWS account grants access to a role, and that role trusts Constellation's OpenID Connect (OIDC) issuer for your account only. No access keys change hands.
Destinations receive predictions served through the API and through agents over MCP. Playground runs are not sent. Deliveries do not count toward your plan's predictions.
How the trust works
- Constellation signs a short-lived token for each delivery. Its issuer is Constellation's OIDC issuer, its audience is
sts.amazonaws.com, and its subject is your account ID (acct_...). - Constellation calls AWS STS
AssumeRoleWithWebIdentitywith that token and your role's ARN. - AWS checks the token's signature against the issuer's published keys and checks that the subject is yours. Only then does it return temporary credentials for the role.
- Constellation writes the object with those temporary credentials.
A token for any other Constellation account carries a different subject, so your role refuses it.
What the stack creates
The CloudFormation stack, constellation-destination, creates:
| Resource | Purpose |
|---|---|
| IAM OIDC identity provider | Registers Constellation's issuer with audience sts.amazonaws.com. |
| IAM role | Trusts that provider only when the token's subject is your account ID. |
| Role policy | Allows s3:PutObject on arn:aws:s3:::<bucket>/<prefix>/* and nothing else. |
Its RoleArn output is the value you paste into the Platform. The stack does not create or change the bucket.
Connect
- Open Destinations in the Platform. On the Amazon S3 card, enter the bucket, a prefix (default
constellation), and the bucket's region. - Choose Launch setup in AWS. CloudFormation opens in that region with the template and parameters filled in. Acknowledge that it creates IAM resources and create the stack.
- When the stack shows
CREATE_COMPLETE, copy theRoleArnoutput, paste it into Role ARN, and choose Connect Amazon S3. - The destination is Pending while Constellation writes a check object, then Active, or Error with the reason AWS gave.
Use Test on a connected destination to write another check object. You can connect up to three destinations per account.
Object layout
s3://<bucket>/<prefix>/model=<family>/date=YYYY-MM-DD/hour=HH/<object>.jsonl.gz
family is snr or demand. Dates and hours are UTC. Each line is one prediction, with the fields of an item from GET /predictions:
{"link_id": "link-a", "horizon_minutes": 1, "predicted_at": "2026-10-06T12:00:05Z", "model_version": "snr-baseline-v1", "feature_view_id": "snr-features-v1", "model_release_id": "snr-release-example", "value": {"snr_db_p10": 8.99, "snr_db_p50": 9.33, "snr_db_p90": 9.66}}
Query with Athena
Partition projection lets Athena find new hours without a crawler. Replace the bucket and prefix:
CREATE EXTERNAL TABLE constellation_predictions (
link_id string,
horizon_minutes int,
predicted_at string,
model_version string,
feature_view_id string,
model_release_id string,
value map<string, double>
)
PARTITIONED BY (model string, `date` string, `hour` string)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://my-fleet-data/constellation/'
TBLPROPERTIES (
'projection.enabled' = 'true',
'projection.model.type' = 'enum',
'projection.model.values' = 'snr,demand',
'projection.date.type' = 'date',
'projection.date.format' = 'yyyy-MM-dd',
'projection.date.range' = '2026-01-01,NOW',
'projection.hour.type' = 'integer',
'projection.hour.range' = '0,23',
'projection.hour.digits' = '2',
'storage.location.template' = 's3://my-fleet-data/constellation/model=${model}/date=${date}/hour=${hour}/'
);
-- Weakest links one minute ahead today
SELECT link_id, min(value['snr_db_p50']) AS snr_p50_db
FROM constellation_predictions
WHERE model = 'snr' AND "date" = cast(current_date AS varchar) AND horizon_minutes = 1
GROUP BY link_id
ORDER BY snr_p50_db
LIMIT 10;
Demand rows carry offered_bytes_p10, offered_bytes_p50, and offered_bytes_p99 in value.
Revoke
Delete the constellation-destination stack. The role disappears, so the next delivery fails and the destination shows Error. Then choose Remove on the destination to stop attempts. Objects already written stay in your bucket.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
Error mentioning AssumeRoleWithWebIdentity or AccessDenied on the role | The ARN is not the stack's RoleArn output, or the stack was created for another Constellation account. Launch setup again from this account's Destinations page. |
Error mentioning AccessDenied on PutObject | The bucket or prefix differs from the stack's parameters, or a bucket policy denies the role. Encrypted buckets using a customer managed KMS key also need the key policy to allow the role kms:GenerateDataKey. |
Error mentioning NoSuchBucket or a region | The bucket name or region is wrong. Remove the destination and connect it again with the right values. |
| Active, but no new objects | Destinations send only predictions that are served. Request forecasts through the API or an agent. |
| Not available in this environment | Destinations are switched off in this environment. Read forecasts from the API instead. |