Amazon

Amazon Selling Partner API: LWA OAuth plus restricted data tokens for PII, reads orders, listings, inventory and settlements, writes price and stock.

Amazon is the largest marketplace a seller can list on in most of the countries we care about, and in India it is one of the two horizontal marketplaces that matter alongside Flipkart. Every seller account on amazon.in, amazon.com, amazon.co.uk and the rest is reachable through one programme, the Selling Partner API (SP-API), which replaced the older Marketplace Web Service (MWS). SP-API is the richest connector in this whole section: it exposes orders, catalogue, listings, offers, inventory, inbound and outbound fulfilment, finances, reports and push notifications, and it lets us write price and stock back. It is also the strictest: access is gated on a registered developer profile, personally identifiable information sits behind a second token, and almost every operation has a published rate limit measured in fractions of a request per second.

At a glance

What it is

Amazon operates around twenty country stores under one seller platform. A seller registers in one store, is given a merchant token (the seller id), and can then be authorised into further stores in the same region group. For India the store is amazon.in, the seller panel is sellercentral.amazon.in, and the marketplace id is A21TJRUUN4KGV. Fulfilment comes in three shapes that the API distinguishes: merchant fulfilled network (MFN, the seller ships), Amazon fulfilled network (AFN or FBA, Amazon ships from its fulfilment centres), and Easy Ship in India, where the seller packs and Amazon collects.

Amazon is a pure marketplace for our purposes: the seller never owns the customer relationship, and buyer name, email address and shipping address are treated as restricted data that a connector must be separately approved and separately tokenised to read. Order volume per seller is not published by Amazon, but the API is built on the assumption that a mid sized seller has tens of thousands of orders a month, which is why the order search operations are rate limited so hard and why bulk work is pushed into Reports.

API access

There are three things to obtain, in order.

  1. A Seller Central account in at least one store. The account holder has to be the one who registers the developer profile, or has to grant a developer user permission.
  2. A developer profile, registered from Seller Central under Apps and Services, then Develop Apps. You declare whether your application is public (multiple sellers authorise it) or private (self authorisation only), and you declare which roles you need. Roles gate operations: Product Listing, Inventory and Order Tracking, Amazon Fulfillment, Pricing, Finance and Accounting, Selling Partner Insights, and the restricted roles that unlock PII.
  3. An application, which yields an LWA client id (amzn1.application-oa2-client....) and client secret, plus an application id (amzn1.sellerapps.app....) used in the consent URL.

Restricted roles, the ones that return buyer name, email and delivery address, require a separate approval step with a data protection questionnaire covering encryption at rest, access logging and retention. Assume weeks, not days. Build the connector so it works without PII first, then light the PII path up when approval lands.

There is no fee for SP-API access. Amazon publishes official SDKs generated from the OpenAPI models in the amzn/selling-partner-api-models repository, covering Java, C#, PHP, Python, TypeScript, Go, Rust and Swift, and a matching Postman collection. The models directory in that repository is the most reliable source of request and response shapes, because the rendered reference pages are JavaScript rendered and fetch poorly.

Warning

Orders v0 is deprecated. The documentation now directs new integrations to Orders v2026-01-01, which replaces getOrders with searchOrders and folds the separate buyer, address and item calls into getOrder with an includedData parameter. No sunset date for v0 is published as of 2026-09-21. Build against v2026-01-01 and keep the v0 field names only as a mapping layer for anything already in production.

Authentication

SP-API authentication has two layers. The outer layer is Login with Amazon (LWA), which gives a bearer access token. The inner layer, only for operations that return PII, is a restricted data token (RDT) that you request with the LWA token and then use in its place.

AWS Signature Version 4 signing and the AWS STS AssumeRole step that older guides describe are no longer required. A plain bearer token in x-amz-access-token is enough.

Send the seller to the Seller Central consent page for the store they sell in. For India that host is sellercentral.amazon.in.

GET https://sellercentral.amazon.in/apps/authorize/consent
  ?application_id=amzn1.sellerapps.app.2eca283f-9f5a-4d13-b16c-474EXAMPLE57
  &state=ourStateToken
  &redirect_uri=https://connectors.example.com/oauth/amazon/callback

Add &version=beta while the application is still in Draft, otherwise the consent page refuses. On approval Amazon redirects to your URI with three parameters: state, selling_partner_id (the seller's merchant token, which becomes our channel_account_id) and spapi_oauth_code. The code expires in five minutes.

Step 2: exchange the code for a refresh token

curl -X POST https://api.amazon.com/auth/o2/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code' \
  -d 'code=ANxxxxxxxxxxxxxxxxxx' \
  -d 'redirect_uri=https://connectors.example.com/oauth/amazon/callback' \
  -d 'client_id=amzn1.application-oa2-client.xxxxxxxx' \
  -d 'client_secret=xxxxxxxx'

The response carries access_token, token_type of bearer, expires_in and, critically, refresh_token. The refresh token does not expire on a timer. Store it per seller, encrypted, keyed by selling_partner_id and region. Both access_token and refresh_token are capped at 2048 bytes.

Step 3: refresh for an access token, every hour

curl -X POST https://api.amazon.com/auth/o2/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=refresh_token' \
  -d 'refresh_token=Atzr|IwEBIxxxxxxxx' \
  -d 'client_id=amzn1.application-oa2-client.xxxxxxxx' \
  -d 'client_secret=xxxxxxxx'

Access tokens live one hour. Cache per seller and refresh at roughly fifty minutes. Do not refresh per request: the LWA endpoint throttles.

Some operations are grantless, meaning they use the application's own credentials rather than a seller grant. Those use grant_type=client_credentials with an explicit scope: sellingpartnerapi::notifications for the Notifications API, sellingpartnerapi::client_credential:rotation for the Application Management API, and sellingpartnerapi::shipments:track for the Tracking API.

Step 4: call the API

curl -X GET 'https://sellingpartnerapi-eu.amazon.com/orders/v0/orders?MarketplaceIds=A21TJRUUN4KGV&CreatedAfter=2026-09-01T00:00:00Z' \
  -H 'x-amz-access-token: Atza|IwEBIxxxxxxxx' \
  -H 'Accept: application/json'

Regional endpoints

India is served from the Europe endpoint, not the Far East one. This surprises people. Prefix the host with sandbox. for the sandbox equivalents.

Marketplace ids

Restricted data tokens

Operations that return buyer name, email, phone or delivery address are restricted. Under Orders v0 you first ask the Tokens API for an RDT scoped to the exact method and path you are about to call, then send the RDT in x-amz-access-token instead of the LWA token.

POST /tokens/2021-03-01/restrictedDataToken HTTP/1.1
Host: sellingpartnerapi-eu.amazon.com
x-amz-access-token: Atza|IwEBIxxxxxxxx
Content-Type: application/json

{
  "restrictedResources": [
    {
      "method": "GET",
      "path": "/orders/v0/orders",
      "dataElements": ["buyerInfo", "shippingAddress"]
    }
  ]
}

The request accepts up to 50 restrictedResources entries, an optional targetApplication when delegating access to another application, and dataElements drawn from exactly three values: buyerInfo, shippingAddress, buyerTaxInformation. The response is restrictedDataToken plus expiresIn in seconds. The operation is limited to 1 request per second with a burst of 10, so batch the resources you need rather than minting one token per order.

Under Orders v2026-01-01 the RDT step is removed: PII comes back from getOrder with includedData=BUYER or includedData=RECIPIENT, gated on the restricted role rather than on a second token.

Multiple sellers

One application serves many sellers. Keep a row per pair of selling_partner_id and region holding the refresh token, the store marketplace ids the seller authorised, and the cached access token with its expiry. Rate limits are scoped to the pairing of selling partner and application, so one noisy seller does not starve the others, but a shared token cache across sellers is a bug waiting to happen.

Objects we can read

Orders

Two versions are live. searchOrders in v2026-01-01 is the one to build on.

Orders v0: GET /orders/v0/orders. Filters are MarketplaceIds (required), CreatedAfter, CreatedBefore, LastUpdatedAfter, LastUpdatedBefore, OrderStatuses, FulfillmentChannels, PaymentMethods, BuyerEmail, SellerOrderId, AmazonOrderIds, EasyShipShipmentStatuses, ElectronicInvoiceStatuses, IsISPU, StoreChainStoreId, the four delivery date windows, MaxResultsPerPage and NextToken. Pagination is an opaque NextToken echoed back on the same call. Use LastUpdatedAfter for incremental sync, not CreatedAfter, or you will miss status transitions. Orders older than two years are not returned.

Sample response, from the published OpenAPI model for getOrders:

{
  "payload": {
    "CreatedBefore": "1.569521782042E9",
    "Orders": [
      {
        "AmazonOrderId": "902-1845936-5435065", "PurchaseDate": "1970-01-19T03:58:30Z",
        "LastUpdateDate": "1970-01-19T03:58:32Z", "OrderStatus": "Unshipped", "FulfillmentChannel": "MFN",
        "SalesChannel": "Amazon.com", "OrderTotal": {"CurrencyCode": "USD", "Amount": "11.01"},
        "NumberOfItemsShipped": 0, "NumberOfItemsUnshipped": 1, "PaymentMethod": "Other",
        "PaymentMethodDetails": ["Standard"], "MarketplaceId": "ATVPDKIKX0DER",
        "ShipmentServiceLevelCategory": "Standard", "IsPrime": false
      }
    ]
  }
}

OrderStatus values are Pending, Unshipped, PartiallyShipped, Shipped, Canceled, Unfulfillable, InvoiceUnconfirmed and PendingAvailability. A Pending order has no payment cleared yet and its items carry no prices, so do not book revenue on it.

Orders v2026-01-01 replaces this with searchOrders plus getOrder, both taking an includedData list drawn from BUYER, RECIPIENT, FULFILLMENT, PROCEEDS, EXPENSE, PROMOTION, CANCELLATION, PACKAGES, TAX, PAYMENT. Pagination moves to a pagination.nextToken supplied back as paginationToken, and that token expires after 24 hours. The published field renames are:

Status strings also change case: Unshipped becomes UNSHIPPED, PartiallyShipped becomes PARTIALLY_SHIPPED, and the American spelling Canceled becomes CANCELLED.

Order items

GET /orders/v0/orders/{orderId}/orderItems, paginated with NextToken. Sample from the model:

{
  "payload": {
    "AmazonOrderId": "903-1671087-0812628", "NextToken": "2YgYW55IGNhcm5hbCBwbGVhc3VyZS4",
    "OrderItems": [
      {
        "ASIN": "BT0093TELA", "OrderItemId": "68828574383266", "SellerSKU": "CBA_OTF_1",
        "Title": "Example item name", "QuantityOrdered": 1, "QuantityShipped": 1,
        "ItemPrice": {"CurrencyCode": "JPY", "Amount": "25.99"}, "ItemTax": null, "PromotionDiscount": null,
        "IsGift": false
      }
    ]
  }
}

ItemPrice is the gross line total for QuantityOrdered, not a unit price. Divide, or keep the line total and derive the unit price on read.

Address and buyer come from two separate restricted calls in v0. getOrderAddress returns payload.AmazonOrderId plus a ShippingAddress object with Name, AddressLine1, City, StateOrRegion, PostalCode and CountryCode (all PII). getOrderBuyerInfo returns BuyerEmail, BuyerName, BuyerTaxInfo.CompanyLegalName and PurchaseOrderNumber (all PII). Store both encrypted and honour Amazon's 30 day retention rule for order data not needed for tax or legal purposes.

Products and listings

Two different APIs, and the distinction matters. Catalog Items describes Amazon's shared catalogue, keyed by ASIN. Listings Items describes your offer against that catalogue, keyed by your SKU.

GET /catalog/2022-04-01/items/{asin} takes marketplaceIds, includedData and locale. includedData accepts attributes, classifications, dimensions, identifiers, images, productTypes, relationships, salesRanks, summaries, vendorDetails. GET /catalog/2022-04-01/items searches by keywords or by identifiers plus identifiersType from ASIN, EAN, GTIN, ISBN, JAN, MINSAN, SKU, UPC, with pageSize up to 20 and a pageToken cursor. Both are limited to 2 requests per second, burst 2.

Trimmed sample from the published getCatalogItem example:

{
  "asin": "B07N4M94X4",
  "attributes": {
    "brand": [{"language_tag": "en_US", "value": "SAMSUNG", "marketplace_id": "ATVPDKIKX0DER"}],
    "model_number": [{"value": "QN82Q60RAFXZA", "marketplace_id": "ATVPDKIKX0DER"}],
    "item_weight": [{"unit": "pounds", "value": 107.6, "marketplace_id": "ATVPDKIKX0DER"}]
  },
  "identifiers": [
    {
      "marketplaceId": "ATVPDKIKX0DER",
      "identifiers": [
        {"identifier": "0887276302195", "identifierType": "EAN"},
        {"identifier": "00887276302195", "identifierType": "GTIN"},
        {"identifier": "887276302195", "identifierType": "UPC"}
      ]
    }
  ],
  "images": [
    {
      "marketplaceId": "ATVPDKIKX0DER",
      "images": [
        {
          "variant": "MAIN", "link": "https://m.media-amazon.com/images/I/91uohwV+k3L.jpg", "height": 1707,
          "width": 2560
        }
      ]
    }
  ],
  "productTypes": [{"marketplaceId": "ATVPDKIKX0DER", "productType": "TELEVISION"}],
  "salesRanks": [
    {
      "marketplaceId": "ATVPDKIKX0DER",
      "classificationRanks": [{"classificationId": "21489946011", "title": "QLED TVs", "rank": 113}],
      "displayGroupRanks": [{"websiteDisplayGroup": "ce_display_on_website", "title": "Electronics", "rank": 72855}]
    }
  ],
  "summaries": [
    {
      "marketplaceId": "ATVPDKIKX0DER", "brand": "SAMSUNG", "manufacturer": "Samsung", "packageQuantity": 1,
      "browseClassification": {"displayName": "QLED TVs", "classificationId": "21489946011"},
      "itemClassification": "BASE_PRODUCT",
      "itemName": "Samsung QN82Q60RAFXZA Flat 82-Inch QLED 4K Q60 Series (2019) Ultra HD Smart TV"
    }
  ]
}

Listings Items 2021-08-01 has five operations:

getListingsItem takes marketplaceIds, issueLocale and includedData from summaries (the default), attributes, issues, offers, fulfillmentAvailability, procurement, relationships, productTypes. searchListingsItems adds identifiers with identifiersType (ASIN, EAN, FNSKU, GTIN, ISBN, JAN, MINSAN, SKU, UPC), the date windows createdAfter, createdBefore, lastUpdatedAfter, lastUpdatedBefore, plus withIssueSeverity, withStatus and withoutStatus over BUYABLE and DISCOVERABLE, sortBy over sku, createdDate, lastUpdatedDate, sortOrder, pageSize capped at 20, and pageToken. That lastUpdatedAfter plus pageToken pair is how we keep a listings table fresh.

Published getListingsItem example:

{
  "sku": "GM-ZDPI-9B4E",
  "summaries": [
    {
      "marketplaceId": "ATVPDKIKX0DER", "asin": "B071VG5N9D", "productType": "LUGGAGE", "conditionType": "new_new",
      "status": ["BUYABLE"], "itemName": "Hardside Carry-On Spinner Suitcase Luggage",
      "createdDate": "2021-02-01T00:00:00Z", "lastUpdatedDate": "2021-03-01T00:00:00Z",
      "mainImage": {"link": "https://www.example.com/luggage.png", "height": 500, "width": 500}
    }
  ],
  "offers": [
    {
      "marketplaceId": "ATVPDKIKX0DER", "offerType": "B2C", "price": {"currencyCode": "USD", "amount": "100.00"},
      "audience": {"value": "ALL", "displayName": "Sell on Amazon"}
    }
  ],
  "fulfillmentAvailability": [{"fulfillmentChannelCode": "DEFAULT", "quantity": 100}],
  "issues": [
    {
      "code": "90220", "message": "'size' is required but not supplied.", "severity": "ERROR",
      "attributeNames": ["size"], "categories": ["MISSING_ATTRIBUTE", "LISTING"],
      "marketplaceIds": ["ATVPDKIKX0DER"]
    }
  ]
}

status is an array, not a scalar. An empty array means the SKU is neither buyable nor discoverable in that store, which is the real signal for "inactive". Note also that fulfillmentAvailability here is the live purchasable quantity, which can differ from the fulfillment_availability attribute you last submitted.

Inventory

For FBA stock, GET /fba/inventory/v1/summaries. Required parameters are granularityType, which only takes Marketplace, granularityId, which is the marketplace id, and marketplaceIds with at most one entry. Optional: details (boolean, default false, and you almost always want true), startDateTime for an incremental window, sellerSkus with at most 50 entries, sellerSku, and nextToken. Rate limit 2 per second, burst 2.

Response shape from the model. Amazon publishes the schema but not a fully populated example, so the values below are placeholders and the field names are the real ones:

{
  "payload": {
    "granularity": {"granularityType": "Marketplace", "granularityId": "A21TJRUUN4KGV"},
    "inventorySummaries": [
      {
        "asin": "B071VG5N9D", "fnSku": "X001ABCDEF", "sellerSku": "GM-ZDPI-9B4E", "condition": "NewItem",
        "inventoryDetails": {
          "fulfillableQuantity": 42, "inboundWorkingQuantity": 10, "inboundShippedQuantity": 5,
          "inboundReceivingQuantity": 0, "reservedQuantity": {"totalReservedQuantity": 3}
        },
        "lastUpdatedTime": "2026-09-20T11:04:00Z", "totalQuantity": 60
      }
    ]
  },
  "pagination": {"nextToken": "..."}
}

For seller fulfilled stock there is no inventory API. The quantity lives on the listing, in fulfillmentAvailability, and is read through getListingsItem or searchListingsItems with includedData=fulfillmentAvailability.

For a full snapshot rather than a poll, use Reports: GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA for active FBA stock, GET_FBA_MYI_ALL_INVENTORY_DATA to include archived, GET_AFN_INVENTORY_DATA for a SKU and ASIN level view, GET_LEDGER_SUMMARY_VIEW_DATA for opening balance, receipts, orders, returns, adjustments and closing balance, and GET_LEDGER_DETAIL_VIEW_DATA for eighteen months of movements.

Shipments and tracking

For FBA, GET /fba/outbound/2020-07-01/tracking returns package tracking details, and GET /fba/outbound/2020-07-01/fulfillmentOrders/{sellerFulfillmentOrderId} returns the fulfilment order with its shipments. For multi channel fulfilment the same API creates orders: POST /fba/outbound/2020-07-01/fulfillmentOrders with sellerFulfillmentOrderId, displayableOrderId, displayableOrderDate, shippingSpeedCategory, destinationAddress and items. All Fulfillment Outbound operations run at 2 requests per second, burst 30.

For seller fulfilled orders, tracking is something we push rather than pull: POST /orders/v0/orders/{orderId}/shipmentConfirmation (confirmShipment, 2 per second, burst 10) or updateShipmentStatus (5 per second, burst 15). The carrier and AWB the seller supplies there are the only shipment record Amazon holds for MFN.

Inbound to FBA is Fulfillment Inbound v2024-03-20, a multi step workflow: createInboundPlan, listInboundPlans, generatePackingOptions, confirmPackingOption, generatePlacementOptions, confirmPlacementOption, generateTransportationOptions, confirmTransportationOptions, then getShipment, listShipmentBoxes and listShipmentItems. Some v0 operations, notably getLabels and getBillOfLading, are explicitly not deprecated and are still required to finish a shipment. The exact request paths for v2024-03-20 were not captured verbatim from a primary source in this pass, so treat the operation list as confirmed and the paths as to be read off the reference before coding.

Returns and cancellations

There is no synchronous returns API for marketplace orders. Returns arrive through Reports.

Cancellations show up as an order status transition to Canceled on the normal order poll, so no separate feed is needed.

Payments and settlements

Two routes, and we want both.

Finances v0 gives event level detail. All four operations run at 0.5 requests per second, burst 30:

listFinancialEventGroups filters on FinancialEventGroupStartedAfter and FinancialEventGroupStartedBefore, MaxResultsPerPage up to 100, and NextToken. listFinancialEvents uses PostedAfter and PostedBefore. Published example:

{
  "payload": {
    "FinancialEventGroupList": [
      {
        "FinancialEventGroupId": "1", "ProcessingStatus": "PROCESSED", "FundTransferStatus": "TRANSFERED",
        "OriginalTotal": {"CurrencyCode": "USD", "CurrencyAmount": 10.34},
        "BeginningBalance": {"CurrencyCode": "USD", "CurrencyAmount": 55.33},
        "FinancialEventGroupStart": "2020-02-07T14:38:42.128Z", "FinancialEventGroupEnd": "2020-02-07T14:38:42.128Z"
      }
    ],
    "FinancialEvents": {
      "ShipmentEventList": [
        {
          "AmazonOrderId": "444-555-3343433", "PostedDate": "2020-02-05T13:56:00.363Z",
          "ShipmentItemList": [
            {
              "SellerSKU": "456454455464", "OrderItemId": "4565465645646",
              "ItemChargeList": [{"ChargeType": "Tax", "ChargeAmount": {"CurrencyCode": "USD", "CurrencyAmount": 25.37}}],
              "ItemFeeList": [{"FeeType": "FixedClosingFee", "FeeAmount": {"CurrencyCode": "USD", "CurrencyAmount": 25.37}}]
            }
          ]
        }
      ]
    }
  }
}

A financial event group is a settlement period. FundTransferStatus of TRANSFERED (Amazon's spelling, one R) means the payout has left.

The settlement report is the authoritative payout document. Use GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE_V2, which replaced the deprecated GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE and GET_V2_SETTLEMENT_REPORT_DATA_XML. It condenses money into amount-type, amount-description and amount columns.

Note

Settlement reports cannot be requested with createReport and cannot be scheduled by you. Amazon generates them on its own cycle. Poll getReports filtered on the report type and pick up whatever is new.

Customers

There is no customer object. Amazon deliberately does not give sellers a customer record. The closest thing is BuyerEmail and BuyerName on a single order, behind a restricted role and an RDT, and on most stores the email is an anonymised Amazon relay address. Treat every order as a new customer unless you are matching on hashed address, and expect that match to be poor.

Locations

For FBA there is no seller facing warehouse list; stock is reported at marketplace granularity only. For seller fulfilled stock, fulfillmentAvailability.fulfillmentChannelCode of DEFAULT is the seller's own location. Multi location inventory exists as a separate feature with publishLocationLevelInventory and retrieveLocationLevelInventory operations, which we have not verified against a primary reference.

Writing back: listings, price and stock

Amazon gives us two write paths and they use the same schema underneath.

Single SKU: patchListingsItem

PATCH /listings/2021-08-01/items/A1B2C3D4E5/GM-ZDPI-9B4E?marketplaceIds=A21TJRUUN4KGV&issueLocale=en_IN HTTP/1.1
Host: sellingpartnerapi-eu.amazon.com
x-amz-access-token: Atza|IwEBIxxxxxxxx
Content-Type: application/json

Price, using the documented purchasable_offer shape. Offers are identified by the triple of marketplace_id, currency and audience, and the operation is merge:

{
  "productType": "PRODUCT",
  "patches": [
    {
      "op": "merge", "path": "/attributes/purchasable_offer",
      "value": [
        {
          "currency": "INR", "audience": "ALL", "marketplace_id": "A21TJRUUN4KGV",
          "our_price": [{"schedule": [{"value_with_tax": 1499.0}]}]
        }
      ]
    }
  ]
}

A time boxed sale price uses discounted_price with start_at and end_at, for example "discounted_price": [{"schedule": [{"start_at": "2026-01-01", "end_at": "2026-05-30", "value_with_tax": 5.0}]}]. Price guard rails use minimum_seller_allowed_price and maximum_seller_allowed_price with the same schedule and value_with_tax shape. Setting a sub attribute to null inside a merge deletes it, so "discounted_price": null in the same offer object is how a sale is cancelled.

Stock for seller fulfilled SKUs is fulfillment_availability, patched the same way with fulfillment_channel_code of DEFAULT and a quantity. Confirm the exact sub attribute names against the product type schema from the Product Type Definitions API before first use: we read the attribute name and its fulfillmentAvailability read side counterpart from official pages, but did not capture a verbatim write example.

The response is the submission record:

{"sku": "GM-ZDPI-9B4E", "status": "ACCEPTED", "submissionId": "f1dc2914-75dd-11ea-bc55-0242ac130003", "issues": []}

status of ACCEPTED means accepted for processing, not applied. Poll getListingsItem with includedData=issues,offers,fulfillmentAvailability, or subscribe to the listings notifications, to learn whether it took.

Add mode=VALIDATION_PREVIEW to validate without persisting. The response then returns status of VALID or INVALID with a populated issues array:

{
  "sku": "VALIDATION_INVALID", "status": "INVALID", "submissionId": "a1c562c2-1695-11ee-be56-0242ac120002",
  "identifiers": [],
  "issues": [
    {
      "code": "90000900", "message": "The attributes are invalid.", "severity": "ERROR",
      "attributeNames": ["fake_attribute"], "categories": ["INVALID_ATTRIBUTE", "LISTING"],
      "enforcements": {"actions": [{"action": "ATTRIBUTE_SUPPRESSED"}], "exemption": {"status": "NOT_EXEMPT"}}
    }
  ]
}

putListingsItem replaces the whole listing. Attributes you omit are dropped: if the listing had bullet points and your PUT body does not include them, they are removed. Use PATCH for everything except a genuine full rewrite.

Bulk: JSON_LISTINGS_FEED

For thousands of SKUs, single calls at 5 requests per second are too slow. Feeds 2021-06-30 is the answer, and JSON_LISTINGS_FEED is now the only supported listings feed.

Warning

As of 31 July 2025 the legacy feed types POST_PRODUCT_DATA, POST_INVENTORY_AVAILABILITY_DATA, POST_PRODUCT_PRICING_DATA and the other XML listing feeds are no longer supported. Submitting them returns a fatal processingStatus. Any connector still built on them needs rewriting onto JSON_LISTINGS_FEED.

The sequence is seven steps.

  1. POST /feeds/2021-06-30/documents with {"contentType": "application/json; charset=UTF-8"}. Rate 0.5 per second, burst 15. Response:

    {
      "feedDocumentId": "3d4e42b5-1d6e-44e8-a89c-2abfca0625bb",
      "url": "https://d34o8swod1owfl.cloudfront.net/Feed_101__POST_PRODUCT_DATA_.xml"
    }
    

    The feedDocumentId expires after two days.

  2. Build the feed document. It carries a header with the seller id, a version and an issue locale, and a messages array where each message has a messageId, a sku, an operationType such as UPDATE or PATCH or DELETE, a productType, and either attributes or patches in exactly the shape patchListingsItem accepts. We did not capture a verbatim schema sample, so build the first document against the JSON_LISTINGS_FEED schema page before relying on the field list.

  3. PUT the bytes to the pre signed url with the same content type you declared. No SP-API auth headers on this call.

  4. POST /feeds/2021-06-30/feeds, rate 0.0083 per second, burst 15, so roughly one submission every two minutes sustained:

    {
      "feedType": "JSON_LISTINGS_FEED", "marketplaceIds": ["A21TJRUUN4KGV"],
      "inputFeedDocumentId": "3d4e42b5-1d6e-44e8-a89c-2abfca0625bb"
    }
    

    marketplaceIds accepts up to 25 entries, so one feed can fan a price change across every store the seller has.

  5. Poll GET /feeds/2021-06-30/feeds/{feedId}, rate 2 per second, burst 15. processingStatus moves IN_QUEUE, IN_PROGRESS, then one of DONE, CANCELLED, FATAL. Under load a feed can take up to eight hours.

    {
      "feedId": "FeedId1", "feedType": "POST_PRODUCT_DATA", "processingStatus": "CANCELLED",
      "resultFeedDocumentId": null
    }
    
  6. GET /feeds/2021-06-30/documents/{resultFeedDocumentId} returns a url and an optional compressionAlgorithm.

  7. Download and parse the processing report. DONE does not mean every message succeeded. The report carries per message results, and a feed can be DONE with every message rejected.

Better than polling: subscribe to FEED_PROCESSING_FINISHED on the Notifications API and drive step 6 off the event.

Webhooks and notifications

Notifications API v1. Amazon does not POST to your HTTPS endpoint. You own an SQS queue or an EventBridge bus and Amazon writes into it.

Destinations are created once per application with a grantless client_credentials token scoped to sellingpartnerapi::notifications. Subscriptions are created per seller with that seller's token.

{"name": "SQSDestination", "resourceSpecification": {"sqs": {"arn": "arn:aws:sqs:us-east-2:444455556666:queue1"}}}

EventBridge instead:

{
  "name": "EventBridgeDestination",
  "resourceSpecification": {"eventBridge": {"accountId": "123456789012", "region": "us-east-1"}}
}

The response returns a destinationId, for example 9e7a83ee-7730-11e9-8f9e-2a86e4085a59, inside a payload object that echoes the name and the resource.

Subscribe, optionally with a filter so a busy notification type does not flood the queue:

{
  "payloadVersion": "1.0", "destinationId": "3acafc7e-121b-1329-8ae8-1571be663aa2",
  "processingDirective": {
    "eventFilter": {
      "marketplaceIds": ["ATVPDKIKX0DER", "A2EUQ1WTGCTBG2"],
      "aggregationSettings": {"aggregationTimePeriod": "FiveMinutes"}, "eventFilterType": "ANY_OFFER_CHANGED"
    }
  }
}

The response returns payload.subscriptionId, for example 7fcacc7e-727b-11e9-8848-1681be663d3e, with the payloadVersion and destinationId echoed back.

The notification types we care about are ORDER_CHANGE, ANY_OFFER_CHANGED and its B2B twin B2B_ANY_OFFER_CHANGED, FBA_INVENTORY_AVAILABILITY_CHANGES, FEED_PROCESSING_FINISHED, REPORT_PROCESSING_FINISHED, the listings item status and issues notifications, ACCOUNT_STATUS_CHANGED, BRANDED_ITEM_CONTENT_CHANGE (EventBridge only), DETAIL_PAGE_TRAFFIC_EVENT and EXTERNAL_FULFILLMENT_SHIPMENT_STATUS_CHANGE. Filtering uses CEL expressions in filterExpression on types that support it. All Notifications operations run at 1 request per second, burst 5.

Verification is not an HMAC signature: the trust boundary is the AWS IAM policy on your queue or bus, which must allow Amazon's notification principal to write. Retries and dead lettering are SQS's, configured by you. ORDER_CHANGE does not carry PII, so an order notification is a trigger to call getOrder, not a replacement for it.

Rate limits and pagination

Every operation has a published rate and burst. The model is a token bucket: tokens refill at the rate, the bucket holds at most the burst, one token per call, empty bucket returns HTTP 429 with a QuotaExceeded error. Limits are scoped to the pair of selling partner and application, and are separate per Amazon store group, so a seller on amazon.in and a seller on amazon.com do not share a bucket.

Read the x-amzn-RateLimit-Limit response header for the rate actually applied to your account and application pair, because some operations run dynamic usage plans that Amazon resizes against the seller's business metrics rather than against your request volume. The header is best effort: it appears on 20x, 400 and 404 responses and may be absent under load or on other errors, so never make it a hard dependency.

Practical cadence. searchOrders at 0.0056 per second is one call every three minutes sustained, with 20 stored up. So: poll orders every fifteen minutes using LastUpdatedAfter, and use ORDER_CHANGE notifications for anything that needs to be faster. Pull inventory once an hour. Refresh listings with searchListingsItems on lastUpdatedAfter once an hour. Pull finances daily and settlement reports on arrival. Push price and stock changes through patchListingsItem when they are few and through JSON_LISTINGS_FEED when a run exceeds a few hundred SKUs, because createFeed at one call per two minutes is fine when each call carries ten thousand messages.

Pagination models differ by API and there is no single convention:

  • Orders v0, Finances v0, FBA Inventory: NextToken or pagination.nextToken, echoed back on the same operation with all other parameters unchanged.
  • Orders v2026-01-01: pagination.nextToken supplied back as paginationToken, expiring after 24 hours, with maxResultsPerPage and includedData the only parameters you may change between pages.
  • Catalog Items, Listings Items: pageToken, with pageSize capped at 20.
  • Reports, Feeds: nextToken with pageSize up to 100.

Mapping to the unified model

orders

order_items

listings

inventory

shipments

returns and settlements

Returns come from the returns reports rather than an API, so return_id maps to the RMA id in the returns report, reason to the return reason code and status to the disposition. Settlements map from GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE_V2: settlement_id from the settlement id column, period_start and period_end from the settlement start and end columns, order_id from the order id column, and fees[] from the amount-type, amount-description and amount triple. The FinancialEventGroupId from Finances v0 is the same settlement period expressed as an object, which makes it a useful cross check.

Gaps and open questions

  • The exact fulfillment_availability write body for patchListingsItem was not captured verbatim from a primary source in this pass. The attribute name and the read side fulfillmentAvailability shape are confirmed; validate the write shape against the Product Type Definitions schema with mode=VALIDATION_PREVIEW before shipping.
  • The JSON_LISTINGS_FEED document schema and its processing report structure were not captured verbatim. The listings feed schema page is the source to read next.
  • Fulfillment Inbound v2024-03-20 operation paths were not captured verbatim, only the operation names and the workflow order.
  • No sunset date is published for Orders v0 as of 2026-09-21, only a deprecation notice. Plan the migration anyway.
  • India specifics not verified here: whether Easy Ship shipment states are fully exposed through EasyShipShipmentStatuses on getOrders in v2026-01-01, and whether the India GST invoice reports carry HSN codes at line level.
  • Multi location inventory (publishLocationLevelInventory, retrieveLocationLevelInventory) is listed in the documentation index but was not read.
  • The rendered API reference pages at developer-docs.amazon.com/sp-api/reference/... are JavaScript rendered and return no content to a plain fetcher. Everything in this page that is marked verbatim came from either the markdown mirrors (append .md to a docs URL) or the OpenAPI models on GitHub. If a field here disagrees with the rendered reference, the rendered reference wins.

Sources