AWS Marketplace guide
For the teams that list a Datacove product on AWS Marketplace and build the tenant website's signup page. It covers the AWS seller account, the platform settings, the buyer's first key, how buyers call the API, and how usage and revenue reach AWS and our reports.
01How it works
A marketplace product belongs to one tenant and lists the workflows its buyers may run. Each AWS buyer becomes a subscription under that product, holding its own mk_ API keys. AWS bills the buyer; Datacove reports each buyer's usage to AWS every hour.
Two ways to price a product
Pay as you go: a price per successful call, reported to AWS as usage. Plans: AWS contracts with a price per term and a call allowance (or unlimited); when the allowance runs out, calls are either refused or billed as overage. A product uses exactly one of the two, chosen when it is created.
Two ways to hand over the key
Redirect (AWS "SaaS product"): the buyer reveals the key once on the tenant's signup page. Quick Launch (AWS "API-Based Agents & Tools"): the same signup, and the key is also stored in the buyer's own AWS Secrets Manager. AWS fixes this per listing, so it is set per product.
| Subscription status | Meaning |
|---|---|
| pending_registration | AWS has told us about the buyer, but they haven't finished the signup page. |
| pending_activation | Signup is done; waiting for AWS to confirm the subscription. Usually seconds. |
| active | Keys work. |
| suspended | Keys are blocked: by an administrator, or by an AWS payment advisory. |
| cancel_pending | The buyer is leaving. Keys are blocked and final usage is reported straight away. |
| ended | The AWS subscription is over. Keys never work again. |
| failed | AWS could not set the subscription up (for example a payment failure). |
02AWS seller setup
Done once per seller AWS account, in us-east-1, where the AWS Marketplace APIs run. Every value below is saved on the tenant (section 03), never in server configuration, because each tenant can sell from its own account.
Event queue
- Create a standard SQS queue (not FIFO), plus a second one as its dead-letter queue with maximum receives 5. Visibility timeout 60 seconds, retention 4 days or more. The backend reads it every minute and deletes each message once stored.
- Give the queue an access policy that lets SNS and EventBridge deliver to it:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowMarketplaceSns",
"Effect": "Allow",
"Principal": { "Service": "sns.amazonaws.com" },
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:us-east-1:<seller-account-id>:<queue-name>",
"Condition": { "ArnEquals": { "aws:SourceArn": [
"arn:aws:sns:us-east-1:<aws-owned-account>:aws-mp-subscription-notification-<product-code>"
] } }
},
{
"Sid": "AllowMarketplaceEventBridge",
"Effect": "Allow",
"Principal": { "Service": "events.amazonaws.com" },
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:us-east-1:<seller-account-id>:<queue-name>",
"Condition": { "ArnEquals": {
"aws:SourceArn": "arn:aws:events:us-east-1:<seller-account-id>:rule/<rule-name>"
} }
}
]
}SNS topics (older products)
If the listing shows an SNS topic, subscribe the queue to it (protocol Amazon SQS). For a plans product, also subscribe to the aws-mp-entitlement-notification-<product-code> topic if listed. Raw message delivery can be on or off.
EventBridge rule (all products)
On the default event bus, create a rule with the pattern { "source": ["aws.agreement-marketplace"] } and the queue as its target, passing the full event. Receiving the same change from SNS and EventBridge is harmless; each message is stored once.
Credentials
Create one IAM policy and attach it either to an IAM user whose access keys are saved on the tenant, or to a role the backend assumes with an external ID.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "MarketplaceApis",
"Effect": "Allow",
"Action": [
"aws-marketplace:ResolveCustomer",
"aws-marketplace:BatchMeterUsage",
"aws-marketplace:GetEntitlements",
"aws-marketplace:DescribeAgreement",
"aws-marketplace:GetAgreementTerms",
"aws-marketplace:DescribeEntity",
"aws-marketplace:PutDeploymentParameter"
],
"Resource": "*"
},
{
"Sid": "EventQueue",
"Effect": "Allow",
"Action": ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"],
"Resource": "arn:aws:sqs:us-east-1:<seller-account-id>:<queue-name>"
},
{
"Sid": "BillingDataFeed",
"Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetObject"],
"Resource": ["arn:aws:s3:::<feed-bucket>", "arn:aws:s3:::<feed-bucket>/*"]
}
]
}| Permission | Used for |
|---|---|
| ResolveCustomer | Identifying the buyer at the fulfillment URL. |
| BatchMeterUsage | Hourly usage reports (pay as you go and overage). |
| GetEntitlements | Reading a plans buyer's contract tier. |
| DescribeAgreement, GetAgreementTerms | Matching agreement events to a product, and the buyer's agreed prices for revenue. |
| DescribeEntity | Checking a product against its listing. |
| PutDeploymentParameter | Quick Launch products: storing the key in the buyer's AWS account. |
| s3:ListBucket, s3:GetObject | Optional billing reconciliation from the seller data feed. |
Listing settings
The listing and the Datacove product must agree on these, or usage is rejected by AWS or priced wrongly in reports.
| Listing field | Set to |
|---|---|
| Fulfillment URL | The product's fulfillmentUrl, copied exactly as the API returns it (section 03). It already includes any deployment path prefix. |
| Pricing model | Pay as you go: "SaaS subscriptions" with one usage dimension. Plans: "SaaS contracts" with one tier per plan, "with pay-as-you-go" when the product bills overage. |
| Dimension API identifiers | Exactly the product's dimensionKey values: the pay-as-you-go dimension, each plan's dimension, and the overage dimension. |
| Prices and durations | The same prices, and each plan's durationMonths (1, 12, 24 or 36). AWS charges what the listing says; the product's copy drives allowances, the signup page and revenue estimates. |
While the listing is limited, allow a test AWS account, subscribe from it and choose Set up your account. You should land on the signup page with ?session=, finish signup, reveal the key and make a call. A few minutes after the next full hour the usage report shows sent (section 07). Use a nominal price while testing.
Billing data feed (optional)
To replace revenue estimates with what AWS actually invoiced, subscribe the seller account to AWS Marketplace seller data feeds (AWS Marketplace Management Portal → Set up customer data storage), delivered to an encrypted S3 bucket in the seller account. Save the bucket on the tenant as dataFeedBucket, with dataFeedPrefix if the feeds live under a key prefix. The billing event feed is then imported every day (section 06).
03Platform setup
Both steps are SUPER_ADMIN calls, usually made from the admin panel.
Tenant settings
Partial update; send only what changes. GET on the same path returns the settings with secrets replaced by has… flags. Setting isEnabled: true is refused until the credentials for the chosen mode, queueUrl and signupPageUrl are all set.
| Field | Type | Notes |
|---|---|---|
| isEnabled | boolean | Turns marketplace on for this tenant. |
| sellerAccountId | string | The seller AWS account, for reference. |
| region | string | Where the queue and feed bucket live. Default us-east-1. |
| credentialMode | "access_keys" | "assume_role" | How the backend authenticates to the seller account. |
| accessKeyId, secretAccessKey | string | access_keys mode. |
| roleArn, externalId | string | assume_role mode. |
| queueUrl | https URL | The event queue from section 02. |
| signupPageUrl | https URL | The tenant website page that finishes signup (section 04). |
| supportEmail | Shown to buyers on the signup page. | |
| dataFeedBucket, dataFeedPrefix | string | Optional billing reconciliation. An empty string turns it off. |
Products
Creates the product and returns it with its fulfillmentUrl, the address to paste into the listing. pricing.type can't change later; create a new product (and listing) to switch.
{
"tenantId": "<tenant id>",
"name": "URL Threat Check API",
"externalProductId": "prod-xxxxxxxxxxxx",
"productCode": "<product code>",
"fulfillmentMode": "redirect",
"notificationChannel": "eventbridge",
"pricing": {
"type": "pay_as_you_go",
"payAsYouGo": { "dimensionKey": "api_call", "pricePerCallUsd": 0.05 }
},
"allowedWorkflows": ["wf-sec-001"],
"keyLifetimeDays": 365
}"pricing": {
"type": "plans",
"plans": [{
"key": "starter",
"name": "Starter",
"dimensionKey": "starter_tier",
"priceUsd": 499,
"durationMonths": 12,
"callLimit": 10000,
"resetEvery": "monthly"
}],
"onLimit": {
"action": "overage",
"dimensionKey": "overage_call",
"pricePerCallUsd": 0.08
}
}| Field | Notes |
|---|---|
| externalProductId, productCode | From the listing. The product id starts with prod-. |
| fulfillmentMode | redirect for an AWS "SaaS product", quick_launch for "API-Based Agents & Tools". |
| notificationChannel | sns if the listing shows an SNS topic, otherwise eventbridge. |
| allowedWorkflows, allowedWorkflowCategories | What buyers may run. "*" allows everything the tenant offers. |
| callLimit | null for unlimited. resetEvery is monthly or term. |
| onLimit.action | block refuses calls once the allowance is used; overage bills them on the overage dimension. |
| keyLifetimeDays | Optional expiry for buyer keys. Omit for keys that don't expire. |
Also available: GET /marketplace/products/v1/:pageNo/:pageSize, GET/PATCH/DELETE /marketplace/products/v1/:id, and POST /marketplace/products/v1/:id/aws-check, which compares the product with its listing (dimensions and prices) and is the quickest way to prove the credentials work.
For the buyer API docs (section 04) to show what each workflow returns, set its optional responseExample (one real successful response) and/or responseSchema (a JSON Schema of it) with PATCH /workflows/v1/:id (SUPER_ADMIN or DEVELOPER). Both are descriptive only; they never change what a run returns. Send null to remove one.
04Tenant signup page
The tenant website owns the page and its design. AWS posts the buyer to the fulfillment URL; the backend identifies them with AWS and redirects the browser to signupPageUrl with either ?session=<token> or ?error=<code>. The page then talks to the API with the tenant website's own dk_ key.
| ?error= | What to show |
|---|---|
| invalid_token | The AWS link expired or was already used. Ask the buyer to choose Set up your account again in AWS Marketplace. |
| product_mismatch | The subscription is for a different product. Ask the buyer to start again from the right listing. |
| product_unavailable | The product is not open for signups. Show the support email. |
| setup_failed | A temporary problem. Ask the buyer to try again shortly. |
Everything the page renders: product name, pricing, subscription status, whether signup is done, and whether the key can be revealed. While status is pending_activation, poll every few seconds until it becomes active.
{
"productName": "URL Threat Check API",
"pricing": { "type": "pay_as_you_go", "payAsYouGo": { "dimensionKey": "api_call", "pricePerCallUsd": 0.05 } },
"status": "pending_activation",
"isRegistered": true,
"isFreeTrial": false,
"contact": { "name": "Priya Sharma", "email": "priya@example.com", "company": "Example Corp" },
"hasKey": false,
"canRevealKey": false,
"supportEmail": "support@example.com",
"expiresAt": "2026-10-15T10:30:00.000Z"
}Body { "name", "email", "company" } (each required, up to 200 characters). Returns the session again. 409 SUBSCRIPTION_CLOSED if the AWS subscription already ended or failed.
Issues the buyer's first key and returns { apiKey, apiKeyId, expiresAt } once. Show it with a copy button and a "won't be shown again" warning. 409 SUBSCRIPTION_NOT_ACTIVE until AWS confirms; 409 KEY_ALREADY_ISSUED if a working key exists.
The page works the same way. Once the key is issued, it is also stored in the buyer's AWS Secrets Manager under the name api-key, as soon as AWS has sent the buyer's agreement. Tell the buyer they can return to AWS Marketplace to finish launching.
Publishing API docs
The tenant website can also host the buyers' API reference, generated live from the product, so it never goes stale. Fetch the document from the website's server with the same dk_ key and render it with any OpenAPI viewer (Redoc, Scalar, Swagger UI). Buyer keys and other callers are refused.
An OpenAPI 3.0 document. Query product (the product's publicRef) is optional when the tenant sells one product; otherwise 400 PRODUCT_REQUIRED lists the choices in error.products. 404 for a product the tenant doesn't sell. Cached for 30 minutes.
The tenant's active products: [{ publicRef, name, pricingType }], for a page that offers more than one.
| The document contains | Built from |
|---|---|
POST /workflows/v1/execute, with a request shape and example per workflow | The workflows the product allows: their names, input schemas and placeholders, and response examples or schemas where set. |
One POST /workflows/v1/tools/{workflowId} per workflow | The same workflows, as agent tools. |
| The agent spec, key rotation, and report polling (only when a workflow returns a report) | The live API definitions. |
servers, the x-api-key security scheme, the error shape | The deployment's public address and the platform's error envelope. |
x-marketplace: productName, publicRef, pricing, supportEmail, workflows | The product's listing prices and the tenant's support email, for the page around the reference. |
Workflows that need a file upload are left out, as they are from the agent spec. Point the signup page's "API docs" link at the page that renders this document.
05Calling the API as a buyer
Buyers send their key as x-api-key: mk_… on every call, from their own servers or an agent gateway. There is no token exchange and no browser origin check. The key works only while the subscription is active.
Running workflows
The same call every API-key integration uses: { "id": "<workflowId>", "input": { … } }. Only the product's workflows are allowed; anything else is 404. Each successful run is billed; a failed run is not, and gives back its plan allowance.
curl -X POST "$BASE_URL/workflows/v1/execute" \
-H "x-api-key: mk_live_…" \
-H "Content-Type: application/json" \
-d '{ "id": "wf-sec-001", "input": { "url": "https://example.com/login" } }'# Queue it, then poll the report every 5 s
curl -X POST "$BASE_URL/workflows/v1/execute?callAsync=true" …
# → { "success": true, "eventId": "…" }
curl "$BASE_URL/workflows/reports/v1/<eventId>" -H "x-api-key: mk_live_…"Use callAsync=true for workflows that take minutes, so the request isn't cut off by a network timeout. The report poll answers { "status": "GENERATING" } while running, then the result. A buyer can only read its own reports.
Agent tools
An OpenAPI document with one tool per workflow in the product, each with a short operationId (for example run_url_threat_check) and its own typed request body. Workflows that need a file upload are left out. Load it into an agent gateway such as AWS Bedrock AgentCore and configure the x-api-key header there; the spec itself carries no auth.
What each tool calls: the workflow's inputs are the body itself, with no { id, input } wrapper. JSON only. Returns 200 with the result. Billing, allowances and errors are the same as for execute.
Sellers get the same document for the listing from GET /marketplace/products/v1/:id/agent-spec (SUPER_ADMIN).
Rotating a key
No body. Authenticated by the key being replaced; returns a new { apiKey, apiKeyId, expiresAt } once. The old key keeps working for 24 hours (or until its own expiry, if sooner) so the buyer can switch over. At most 5 keys at a time (409 TOO_MANY_KEYS). For Quick Launch products the new key also replaces the one in the buyer's Secrets Manager. A buyer who lost their key asks support, who reissue one (section 07).
06Metering and revenue
Hourly usage
A few minutes past each hour, the previous hour's successful billable runs are reported to AWS with BatchMeterUsage, one record per buyer and dimension. A report is never sent twice. AWS refuses usage 24 hours or more old, so failures are retried within that window.
Estimated revenue
Accepted usage is added to one payment per buyer per UTC day, and each plan term bought to one payment, priced at the buyer's agreed rate from AWS (or the product's list price). These payments have provider aws_marketplace, currency USD and status estimated.
Reconciled revenue
With the billing data feed set up, each AWS invoice becomes one payment: pending when invoiced, completed once AWS collects it, refunded if forgiven, with AWS's listing fee in metadata.listingFee. The estimates it covers become superseded.
Dashboards show marketplace revenue in a separate USD bucket, and workflow stats show marketplace runs and revenue in their own block; neither is ever mixed into INR or CAD earnings. When an invoice replaces estimates, the dashboard is corrected by the difference, so nothing is counted twice.
07Operations
SUPER_ADMIN endpoints for support and finance. Every change is written to the audit log.
| Call | Use |
|---|---|
| GET /marketplace/subscriptions/v1/:pageNo/:pageSize | Buyers. Filters tenantId, productId, status, search (email, company or AWS account). |
| GET /marketplace/subscriptions/v1/:id | One buyer: status history, key metadata (never the key), Quick Launch delivery, and a usage summary (plan allowance, 30-day runs, units reported, revenue). |
| POST …/subscriptions/v1/:id/suspend | Body { "reason" }. Blocks the keys at once. AWS keeps billing any contract. |
| POST …/subscriptions/v1/:id/restore | Lifts a suspension, including one from an AWS advisory. |
| POST …/subscriptions/v1/:id/keys | Reissues a lost key: every existing key stops, the new one is returned once. |
| POST …/subscriptions/v1/:id/keys/deliver | Quick Launch: resends the newest key to the buyer's AWS account. Delivery is otherwise automatic and retried every 10 minutes. |
| GET /marketplace/events/v1/:pageNo/:pageSize | Every AWS notification received and how it was handled. Failed ones retry up to 5 times. |
| GET /marketplace/metering/v1/:pageNo/:pageSize | Usage reports per buyer, dimension and hour, with AWS's error for failed ones. |
| POST /marketplace/metering/v1/:id/retry | Re-queues a failed report while AWS still accepts it (within 23 hours). |
| GET /marketplace/billing/v1/:pageNo/:pageSize | Imported AWS billing events. unmatched=true lists rows for an account or product no buyer matches. |
| POST /marketplace/billing/v1/import | Body { "tenantId" }. Runs the daily billing feed import now. Safe to repeat. |
| Symptom | Check |
|---|---|
| The signup page stays on "waiting for AWS" | No subscribe event arrived: the SNS subscription or EventBridge rule, the queue policy, and the event log. |
Events show ignored with "no marketplace product configured" | The product code or prod- id on the product doesn't match the listing. |
| Usage reports fail | Dimension keys must match the listing exactly; CustomerNotSubscribed means the buyer left on AWS. |
| Quick Launch key not delivered | The subscription's keyDelivery.lastError; a missing PutDeploymentParameter permission is the usual cause. |
08Errors
Errors use the platform envelope. Branch on the status, then on error.code.
{
"statusCode": 429,
"timestamp": "2026-10-15T09:30:00.000Z",
"path": "/api/workflows/v1/execute",
"error": {
"code": "PLAN_LIMIT_REACHED",
"message": "Your plan's call allowance is used up. It resets on 2026-11-01T00:00:00.000Z.",
"resetsAt": "2026-11-01T00:00:00.000Z"
}
}| Status | error.code | Where | What to do |
|---|---|---|---|
| 401 | — | any buyer call | The key is wrong, expired, rotated out, or the subscription isn't active. |
| 404 | — | execute, tools | The workflow isn't part of this product. |
| 429 | PLAN_LIMIT_REACHED | execute, tools | The plan allowance is used up; it resets at resetsAt. Plain 429s without a code are rate limits. |
| 409 | TOO_MANY_KEYS | rotate | Five keys exist. Ask support to remove old ones. |
| 404 | SESSION_NOT_FOUND | signup | The session is unknown or older than 30 minutes. The buyer starts again from AWS. |
| 409 | SUBSCRIPTION_CLOSED | signup complete | The AWS subscription ended or failed. |
| 409 | SUBSCRIPTION_NOT_ACTIVE | signup key | AWS hasn't confirmed yet. Keep polling the session. |
| 409 | KEY_ALREADY_ISSUED | signup key | The buyer already has a working key. |
| 502 | — | execute, tools | The workflow service failed. Safe to retry; failed runs are never billed. |