Marketplace Offers — Trust Policy
The trust policy on the FlowStateMarketplaceConnector IAM role controls who can assume the
role and under what conditions.
The trust policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::<FLOWSTATE_AWS_ACCOUNT>:role/flowstate-<stage>-backend",
"arn:aws:iam::<FLOWSTATE_AWS_ACCOUNT>:role/flowstate-<stage>-mcp-execution"
]
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "<YOUR_EXTERNAL_ID>"
}
}
}
]
}Fill in your FlowState account id, stage, and ExternalId — your FlowState contact provides all three, or use the values shown in Getting started.
Two principals — both required
| Principal | Why it needs to assume your role |
|---|---|
flowstate-<stage>-backend | Serves the REST API — the FlowState web app and the HubSpot card. |
flowstate-<stage>-mcp-execution | Serves the MCP tools. |
Include both. A trust policy listing only the backend role works through the web app and the
card, then fails with AccessDenied the moment anyone uses Marketplace Offers through an MCP
client.
The CloudFormation parameter and OpenTofu variable are both lists
(FlowstatePrincipalArns / flowstate_principal_arns), so add both ARNs to the list.
sts:ExternalId
The ExternalId is a secret, unique to your tenant, generated by FlowState. It must match exactly what Getting started shows you — no leading or trailing spaces — or the AssumeRole call is denied. It protects against the confused-deputy problem : without it, another FlowState tenant could not trick FlowState into assuming your role, because they don’t have your ExternalId.
The permissions policy
This role’s permissions are an inline policy — attach it directly, there is no AWS-managed policy to reference.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "MarketplaceCatalogList",
"Effect": "Allow",
"Action": ["aws-marketplace:ListEntities"],
"Resource": "*"
},
{
"Sid": "MarketplaceCatalogManage",
"Effect": "Allow",
"Action": [
"aws-marketplace:DescribeEntity",
"aws-marketplace:StartChangeSet",
"aws-marketplace:DescribeChangeSet"
],
"Resource": [
"arn:aws:aws-marketplace:us-east-1:<YOUR_ACCOUNT_ID>:AWSMarketplace/SaaSProduct/*",
"arn:aws:aws-marketplace:us-east-1:<YOUR_ACCOUNT_ID>:AWSMarketplace/Offer/*",
"arn:aws:aws-marketplace:us-east-1:<YOUR_ACCOUNT_ID>:AWSMarketplace/ChangeSet/*",
"arn:aws:aws-marketplace:us-east-1:<YOUR_ACCOUNT_ID>:AWSMarketplace/Seller/*"
]
},
{
"Sid": "MarketplaceAgreementRead",
"Effect": "Allow",
"Action": ["aws-marketplace:SearchAgreements"],
"Resource": "*",
"Condition": {
"ForAllValues:StringEquals": {
"aws-marketplace:AgreementType": ["PurchaseAgreement"]
},
"StringEquals": {
"aws-marketplace:PartyType": "Proposer"
}
}
}
]
}Replace <YOUR_ACCOUNT_ID> with your AWS account id.
Note the action prefix: it’s aws-marketplace:, not marketplace-catalog:, even though the
API and SDK client are both named marketplace-catalog.
Region: the AWS Marketplace Catalog API exists in us-east-1 only, regardless of which
region your seller account otherwise operates in. Keep the resource ARNs above pinned to
us-east-1.
What each statement is for:
MarketplaceCatalogList— find your SaaS products and offers.MarketplaceCatalogManage— read a product or offer, create/clone/release/expire offers (all three actions are needed for every one of those operations), and read your seller profile’s provisioned currencies so the offer form can show only currencies you’re actually able to be paid in.MarketplaceAgreementRead— list the purchase agreements your offers have produced.
This is a read/write grant on your own Marketplace catalog and a read-only grant on your agreements and seller profile. Nothing outside AWS Marketplace is touched.