Marketplace Selling

SP-API Restricted Data Token: Why Your RDT Returns No PII

How the SP-API restricted data token works: which operations need an RDT, the dataElements and roles that unlock PII, and how to cache RDTs safely.

Long Nguyen Avatar

Long Nguyen

Fullstack Developer · AI Engineer · Researcher

• • 5 min read •

Almost every Amazon integration hits the same wall on day two: the orders come back, but BuyerInfo is empty, the shipping address is a ghost, and the label cannot be printed. The missing piece is the restricted data token — a second, narrower token that Amazon requires before any Selling Partner API operation will hand over customer PII.

The mechanics are not complicated, but they are unforgiving: the token is scoped to exact paths you declare in advance, it expires inside the hour, it is rate limited far more tightly than the operations it unlocks, and it silently returns nothing useful if your application is missing the matching role. This guide covers all four.

What a restricted data token actually is

A restricted data token (RDT) is a short-lived access token issued by the Tokens API that authorizes calls to a specific list of restricted resources — an HTTP method plus a path, optionally narrowed by the type of PII you need. You obtain it by calling createRestrictedDataToken with your ordinary Login with Amazon (LWA) access token, then send the RDT back in the x-amz-access-token header in place of that LWA token when you call the restricted operation.

Three properties define it, and getting any of them wrong is the source of most integration bugs:

  • It is scoped. The token carries the exact resource list you declared. A resource you did not declare is not covered, even if your application is otherwise authorized for it.
  • It is short-lived. Amazon's own request examples return expiresIn: 3600 — one hour. It is a working credential, not a configuration value.
  • It is not a drop-in replacement. An RDT cannot be used for standard, non-restricted SP-API calls. A shared HTTP client that swaps in the RDT globally will break every ordinary call in your integration.

The clearest mental model: the LWA access token answers who is calling. The RDT answers which specific pieces of customer data this caller may touch, for the next hour. They are different questions, which is why Amazon makes you answer both.

One thing an RDT emphatically does not do: grant your application a role it has not been approved for. The token is an authorization envelope, not an approval. More on that below, because it is the single most common reason a correct-looking RDT returns an order with no PII in it.

Which SP-API operations require a restricted data token

Restricted operations are the ones that return customer PII. Amazon maintains the authoritative list in the Tokens API use case guide; as of it spans eleven API groups:

API Restricted operations
Orders getOrders, getOrder, getOrderItems, getOrderAddress, getOrderBuyerInfo, getOrderItemsBuyerInfo, getOrderRegulatedInfo
Reports getReportDocument
Merchant Fulfillment createShipment, getShipment, cancelShipment, cancelShipmentOld
Shipping getShipment
Shipment Invoicing getShipmentDetails
Easy Ship (v2022-03-23) createScheduledPackageBulk
Direct Fulfillment Orders (v1 and v2021-12-28) getOrders, getOrder
Direct Fulfillment Shipping (v1) getShippingLabel, getShippingLabels, createShippingLabels, getPackingSlip, getPackingSlips, getCustomerInvoice, getCustomerInvoices
Direct Fulfillment Shipping (v2021-12-28) getShippingLabel, getPackingSlip, getPackingSlips, getCustomerInvoice, getCustomerInvoices

Two practical readings of that table. First, getOrders is restricted even though the bulk list is where most integrations start — you need an RDT before your very first useful order sync, not later. Second, the restriction follows the data, not the API: in the Reports API only getReportDocument is restricted, because that is the operation that hands back the document containing PII. createReport and getReport are ordinary calls.

How to create a restricted data token

One POST to /tokens/2021-03-01/restrictedDataToken, authenticated with your normal LWA access token. The body declares the resources you intend to call:

POST https://sellingpartnerapi-na.amazon.com/tokens/2021-03-01/restrictedDataToken
x-amz-access-token: Atza|IwEBI...        (your ordinary LWA access token)
content-type: application/json

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

The response is the token plus its lifetime:

{
  "restrictedDataToken": "Atz.sprdt|IQEBLjAsAhRmHjNgHpi0U-Dme37rR6CuUpSR",
  "expiresIn": 3600
}

From there, the only change to your restricted call is the header value — same endpoint, same signing, same everything else, with restrictedDataToken substituted for the LWA access token in x-amz-access-token.

Two limits from the published Tokens API model shape how you should design around this call: restrictedResources accepts a maximum of 50 entries, and createRestrictedDataToken runs at a default usage plan of 1 request per second with a burst of 10. Both numbers matter more than they look, and section six explains why.

dataElements and roles: why your order still comes back without PII

This is the failure that wastes the most time, because nothing errors. The RDT is issued, the call returns HTTP 200, and the PII fields are simply absent. Two separate controls have to line up.

Control one: dataElements. It declares which category of PII you are asking for, and it is required only when the RDT is for getOrder, getOrders or getOrderItems. Omit it there and the order comes back stripped. Three values exist:

dataElements value What it unlocks Role that must be approved
buyerInfo At order level, identifying and tax-related buyer information. At order-item level, gift wrap details and custom order information where available. Tax Remittance or Tax Invoicing
shippingAddress The address information needed to fulfil the order. Direct to Consumer Shipping
buyerTaxInformation The data needed to issue a tax invoice. Tax Invoicing

Control two: the role. Requesting shippingAddress without an approved Direct to Consumer Shipping role does not produce a permissions error you can act on — the RDT is still issued, because the Tokens API itself only requires one of a broad set of roles. The restriction bites at the restricted operation, which returns the order minus the data you are not approved to see. Role approval happens in your developer profile and app registration, not in code, and it is the half of this problem no amount of debugging the request body will fix.

The useful diagnostic rule: if the call errors, suspect the request; if the call succeeds but the field is missing, suspect the role. That one sentence resolves most RDT tickets before anyone opens a packet capture.

A smaller detail worth knowing because it is easy to misread: buyerInfo means something different at the two levels. On an order it is buyer identity and tax data; on an order item it is gift wrap and customization. Teams building gift-message features sometimes request buyerInfo at the order level, see nothing resembling a gift note, and conclude the API does not expose it.

Path matching: the rule behind most RDT authorization failures

The path in a restricted resource is matched against the operation you subsequently call. It is not a prefix, and it is not a namespace. Declaring /orders/v0/orders does not cover /orders/v0/orders/123-1234567-1234567/address — different operation, different path, different resource.

Amazon's model documents three usable shapes:

Path shape Example Covers
Collection /orders/v0/orders getOrders — bulk order retrieval for the selling partner
Specific resource /orders/v0/orders/123-1234567-1234567 getOrder for that one order only
Specific sub-resource /orders/v0/orders/123-1234567-1234567/orderItems getOrderItems for that order
Placeholder /mfn/v0/shipments/{shipmentId} Any of the selling partner's shipments, specified at call time

The placeholder form is the one most teams miss, and it is the difference between a workable design and a token storm. With a literal ID, an RDT is single-purpose: one order, one token. With the documented placeholder form, one token covers the operation across the partner's resources, and you mint per seller and per operation set instead of per record.

Where that is not available for your operation, the 50-resource ceiling is your batching unit: group the orders you are about to process into chunks of up to 50 and request one RDT per chunk, rather than one per order. Either way, the shape of the design decision is the same — mint tokens per batch of work, never per record.

RDT lifetime, caching, and the 1 request-per-second ceiling

Run the arithmetic on the usage plan and the design constraint becomes obvious. At 1 request per second sustained, createRestrictedDataToken tops out around 3,600 tokens an hour. A naive one-RDT-per-order implementation therefore caps your entire integration at roughly 3,600 orders an hour — and it does so by throttling the token endpoint, not the Orders API, which is why the symptom (HTTP 429 from a call nobody is thinking about) rarely points at the cause.

Cache instead, keyed on the resource set rather than on the order:

  • Key by signature. Build the cache key from the sorted method, path and dataElements of every declared resource. Identical resource sets reuse one token; a different set gets its own.
  • Trust expiresIn, do not hardcode 3600. The documented examples return an hour, but the field exists so the server can change its mind. Store an absolute expiry computed from the response.
  • Refresh early. Expire your cache entry a few minutes before Amazon does. A token that dies mid-batch costs a retry; a token refreshed 300 seconds early costs nothing.
  • Never log or persist the token. It is a live credential to customer PII with an hour of validity. It belongs in memory, not in a request log, not in a database column, not in an error trace sent to a third-party monitor.

That last point is the one that turns a technical detail into a compliance problem. Amazon's data protection requirements apply to anything downstream of an RDT, and an exception tracker that captures outbound headers will happily archive your tokens and the PII they fetched. If you are wiring this into a multi-channel system where orders fan out to a WMS, a label service and an accounting ledger, the token boundary and the PII boundary are the same boundary — and that scoping work is usually where marketplace API integration projects either stay clean or quietly stop being auditable.

Using an RDT to download reports that contain PII

Order reports that carry buyer data follow the same rule through a different door. The report workflow is three calls, and only the last one is restricted:

  1. createReport — ordinary LWA token.
  2. getReport, polled until the processing status is done — ordinary LWA token. The response carries a reportDocumentId.
  3. getReportDocument — restricted. Mint an RDT for GET /reports/2021-06-30/documents/{reportDocumentId} and call it with that token.

Note that dataElements plays no part here: it is required only for the three Orders operations. For a report document you declare the method and path and nothing else.

The detail that surprises people: getReportDocument returns a pre-signed download URL, and the download itself is an ordinary HTTPS GET. The RDT authorizes the SP-API call that issues the URL, not the transfer. So a report job can fail with an expired RDT at step three and succeed on retry, while the actual download never touches your token at all — which also means that URL deserves the same handling discipline as the token.

Delegating PII access to another application

Sometimes the application that is authorized by the seller is not the application that needs the PII — a shipping service, a tax invoicing provider, a label vendor. Amazon handles that with delegation rather than with a second seller authorization.

The delegator (your app, authorized by the seller) calls createRestrictedDataToken with a targetApplication field set to the delegatee's application ID, alongside the usual restrictedResources. The resulting RDT authorizes that application to call the declared operations, and you transmit the token and the relevant order ID to the partner over a secure channel. The delegatee never holds a seller authorization of its own.

Two prerequisites are easy to miss because they live outside the code. The delegator must have declared PII delegation, and the types of PII involved, in its app registration form. And the delegatee must be separately approved for the roles behind the data being delegated — Direct to Consumer Shipping for addresses, Tax Remittance or Tax Invoicing for buyer information. Delegation moves authorization, not approval.

The operational consequence worth designing for: delegated RDTs expire on the same clock as any other. A partner handoff built as a one-shot payload will break the moment their queue backs up past the hour. Build the handoff as a refreshable channel, not a fire-and-forget POST.

A token cache that holds up in production

Everything above collapses into one component: a thread-safe cache keyed by resource signature, refreshing ahead of expiry, holding tokens only in memory.

import threading
import time

import requests

RDT_ENDPOINT = 'https://sellingpartnerapi-na.amazon.com/tokens/2021-03-01/restrictedDataToken'


class RestrictedTokenCache:
    '''One cached RDT per declared resource set, refreshed before it expires.'''

    def __init__(self, lwa_token_provider, refresh_margin=300):
        self._lwa = lwa_token_provider      # callable returning a valid LWA access token
        self._margin = refresh_margin       # refresh this many seconds before expiry
        self._entries = {}
        self._lock = threading.Lock()

    @staticmethod
    def _signature(resources):
        return tuple(sorted(
            (r['method'], r['path'], tuple(sorted(r.get('dataElements', []))))
            for r in resources
        ))

    def token_for(self, resources):
        if len(resources) > 50:
            raise ValueError('restrictedResources accepts at most 50 entries per request')

        key = self._signature(resources)
        with self._lock:
            entry = self._entries.get(key)
            if entry and entry['expires_at'] - self._margin > time.monotonic():
                return entry['token']

            response = requests.post(
                RDT_ENDPOINT,
                json={'restrictedResources': resources},
                headers={'x-amz-access-token': self._lwa()},
                timeout=15,
            )
            response.raise_for_status()
            body = response.json()

            self._entries[key] = {
                'token': body['restrictedDataToken'],
                # trust the server's lifetime rather than assuming 3600
                'expires_at': time.monotonic() + body['expiresIn'],
            }
            return self._entries[key]['token']

Used against the bulk orders resource, one cached token now covers an entire hour of order syncing:

cache = RestrictedTokenCache(lwa_token_provider=get_lwa_access_token)

orders_resource = [{
    'method': 'GET',
    'path': '/orders/v0/orders',
    'dataElements': ['buyerInfo', 'shippingAddress'],
}]

rdt = cache.token_for(orders_resource)
orders = requests.get(
    'https://sellingpartnerapi-na.amazon.com/orders/v0/orders',
    params={'MarketplaceIds': 'ATVPDKIKX0DER', 'CreatedAfter': '2026-10-01T00:00:00Z'},
    headers={'x-amz-access-token': rdt},
    timeout=30,
).json()

Three deliberate choices in there. time.monotonic() rather than wall-clock time, so an NTP correction cannot make a live token look expired or an expired one look live. The 50-resource guard raised locally, because discovering that limit through a 400 response during a nightly batch is an avoidable outage. And the signature keyed on dataElements as well as the path, because a token minted for shippingAddress alone must not be reused for a call that expects buyerInfo — that reuse is exactly the silent-missing-field bug from section four, reintroduced by your own cache.

If you are standing up an Amazon integration from scratch, or inheriting one where the PII path was bolted on after launch, we can scope the work with you — tell us what you are integrating and we will come back with an approach and a number.

FAQ

Frequently asked questions

What is a restricted data token in Amazon SP-API?

A restricted data token (RDT) is a short-lived access token from the Tokens API that authorizes calls to restricted operations, meaning the SP-API operations that return customer personally identifiable information. You request it with your normal LWA access token by declaring the exact method and path you intend to call, then send the RDT in the x-amz-access-token header in place of the LWA token for that call.

How long does an SP-API restricted data token last?

Amazon's documented request examples return expiresIn: 3600, so one hour. Read the expiresIn value from the response and compute an absolute expiry from it rather than hardcoding 3600, and refresh a few minutes early so a token does not expire mid-batch.

Why does my order still have no buyer info even with an RDT?

Two controls have to line up. The RDT must declare the right dataElements value (buyerInfo, shippingAddress or buyerTaxInformation), which is required for getOrder, getOrders and getOrderItems. Your application must also be approved for the matching role: Direct to Consumer Shipping for addresses, Tax Remittance or Tax Invoicing for buyer information. Missing the role does not produce an error, the fields simply come back absent. If the call errors, suspect the request; if it succeeds with a missing field, suspect the role.

Do I need a restricted data token for the Reports API?

Only for getReportDocument. createReport and getReport are ordinary calls made with your LWA access token. Once a report is done, mint an RDT for GET /reports/2021-06-30/documents/{reportDocumentId} and use it for getReportDocument. The pre-signed download URL that call returns is fetched with an ordinary HTTPS GET and does not use the token.

Can one restricted data token cover multiple orders?

Yes. restrictedResources accepts up to 50 entries per request, so you can declare a batch of specific order paths in one token. Where a placeholder path form is documented for the operation, one token can cover the operation across the selling partner's resources. Either way, mint per batch of work rather than per order, because createRestrictedDataToken runs at a default of 1 request per second with a burst of 10 and becomes your throughput ceiling long before the Orders API does.

How do I give another application access to PII without a seller authorization?

Use delegation. Your application, which holds the seller authorization, calls createRestrictedDataToken with a targetApplication field containing the other application's ID, then transmits the resulting RDT and the relevant order ID over a secure channel. You must have declared PII delegation in your app registration, and the delegatee must separately hold approval for the roles behind the data being delegated.

Stay updated with Netalith

Get coding resources, product updates, and special offers directly in your inbox.