README
¶
Smart Proxy for SP-API
A reverse proxy that handles SP-API rate limits, caching, and PII redaction so your Amazon integrations stay fast and compliant.
Built by SPIO · Not affiliated with Amazon
Table of Contents
- Why Smart Proxy?
- Quick Start
- Usage
- Merchant ID
- Configuration
- Architecture
- Caching
- Rate Limiting
- Auto-RDT
- DPP Compliance & PII Redaction
- Dashboard
- Development
- Documentation
- Contributing
- Community
- Security
- License
Why Smart Proxy?
If you build on Amazon's Selling Partner API, you know the pain: aggressive rate limits, rotating access tokens that break caching, PII scattered across responses, and zero visibility into what's happening. Smart Proxy sits between your application and SP-API to solve all of that.
| Problem | Smart Proxy Solution |
|---|---|
| Redundant API calls waste quota and money | 4-tier LRU cache with automatic TTL eliminates duplicate GET and POST requests |
| Rate limits cause 429 errors and throttling | Token-bucket rate limiter with automatic queuing and priority lanes |
| PII handling is complex and error-prone | Built-in DPP-compliant PII redaction (REDACT / HASH / OMIT) |
| RDT management is tedious and error-prone | Auto-RDT minting with generic-path caching (one mint covers all orders) |
| No visibility into API usage | Request log browser with filters + Prometheus /metrics endpoint |
| Access tokens rotate hourly, breaking cache keys | Stable merchant-ID based caching across token rotations |
| Managing multiple SP-API regions | Single proxy with EU, NA, and FE on separate ports |
Zero external dependencies. Ships as a single static Go binary with embedded SQLite and SvelteKit dashboard.
Security: do not expose Smart Proxy to the public internet.
The proxy is designed as a sidecar / private-network component. It accepts an
X-SP-Proxy-Merchant-Idheader to identify tenants; an unauthenticated public endpoint would let any caller self-claim any merchant key. The dashboard ships without authentication.Run it on loopback, on a private VPC subnet, or behind an authenticating reverse proxy (mTLS, OAuth, IP allowlist). See SECURITY.md for the full deployment checklist.
Quick Start
Prerequisites
- Docker >= 20.10 (or Go >= 1.25 and Node.js >= 20 for source builds)
1. Docker Compose (recommended)
# Clone the repository
git clone https://github.com/spiohq/smart-proxy.git
cd smart-proxy
# Copy and adjust configuration
cp deploy/example.env .env
# Start the proxy
docker compose up -d
2. Docker Run
docker run -d \
--name smart-proxy \
--restart unless-stopped \
-p 8080:8080 \
-p 8081:8081 \
-p 8082:8082 \
-p 127.0.0.1:9090:9090 \
-v sp-proxy-data:/data \
ghcr.io/spiohq/smart-proxy:latest
3. Build from Source
make build # Builds the Dashboard SPA + Go binary (embeds the SPA via go:embed)
./bin/smart-proxy
Verify
Open http://localhost:9090 to access the dashboard, or:
curl -s http://localhost:8080/health
Ports
| Port | Purpose |
|---|---|
8080 |
EU region proxy -> sellingpartnerapi-eu.amazon.com |
8081 |
NA region proxy -> sellingpartnerapi.amazon.com |
8082 |
FE region proxy -> sellingpartnerapi-fe.amazon.com |
9090 |
Dashboard (request logs, audit) |
[!TIP] Set a port to
0to disable a region:-e SP_PROXY_PORT_FE=0
Usage
Point your SP-API client at the proxy instead of Amazon's endpoint:
- https://sellingpartnerapi-eu.amazon.com/orders/v0/orders
+ http://localhost:8080/orders/v0/orders
The proxy transparently forwards your request to Amazon, handling caching, rate limiting, and logging along the way. All SP-API authentication headers (Authorization, x-amz-access-token) are passed through unchanged.
Request Headers
| Header | Description |
|---|---|
X-SP-Proxy-Merchant-Id: SELLER_XYZ |
Recommended. Stable merchant identifier for cache & rate limits |
X-SP-Proxy-No-Cache: true |
Bypass cache for this request |
X-SP-Proxy-Cache-TTL: 10m |
Override cache TTL for this request |
X-SP-Proxy-Cache-Until: 2025-01-01T00:00:00Z |
Cache until absolute time |
X-SP-Proxy-Throttle-Mode: reject |
Override rate-limit mode (queue / reject / queue-timeout) |
X-SP-Proxy-Priority: high |
Set queue priority (low / normal / high) |
X-SP-Proxy-Force-RDT: true |
Force RDT minting for this request (see Auto-RDT) |
Response Headers
| Header | Description |
|---|---|
X-SP-Proxy-Cache: HIT / MISS / PII_EXCLUDED |
Cache status |
X-SP-Proxy-Queued: true |
Request was queued due to rate limit |
X-SP-Proxy-Queue-Wait-Ms: 1200 |
Time spent in queue (ms) |
X-SP-Proxy-Rate-Limit-Remaining: 0.5 |
Remaining tokens in rate-limit bucket |
Merchant ID: Important for Caching & Rate Limits
[!IMPORTANT] This is the single most impactful setting for cache and rate-limit efficiency.
Send the X-SP-Proxy-Merchant-Id header with every request:
curl http://localhost:8080/orders/v0/orders \
-H "X-Amz-Access-Token: Atza|..." \
-H "X-SP-Proxy-Merchant-Id: MY_SELLER_ID"
Why this matters
Without this header, the proxy falls back to a SHA-256 hash of the access token as merchant identifier. Since SP-API access tokens rotate every hour, this means:
- Cache misses every hour. The same data is fetched again after each token refresh because the cache key changes.
- Rate-limit buckets reset every hour. Token counters start from zero with each new token, losing burst capacity and risking 429s.
With X-SP-Proxy-Merchant-Id, the merchant identity stays stable across token rotations, giving you persistent cache hits, continuous rate-limit tracking, and per-seller isolation when managing multiple seller accounts.
Resolution Priority
| Priority | Source | Example Key |
|---|---|---|
| 1 | X-SP-Proxy-Merchant-Id header |
MY_SELLER_ID |
| 2 | Config-based token mapping | mapped-seller-123 |
| 3 | SHA-256 hash of access token (fallback) | tokenhash:a1b2c3d4e5f6... |
Configuration
All configuration is via environment variables. See deploy/example.env for the complete reference.
Server
| Variable | Default | Description |
|---|---|---|
SP_PROXY_PORT_EU |
8080 |
EU region proxy port (0 = disabled) |
SP_PROXY_PORT_NA |
8081 |
NA region proxy port (0 = disabled) |
SP_PROXY_PORT_FE |
8082 |
FE region proxy port (0 = disabled) |
SP_PROXY_PORT_DASHBOARD |
9090 |
Dashboard web UI port |
SP_PROXY_SHUTDOWN_TIMEOUT |
30s |
Graceful shutdown timeout |
SP_PROXY_DASHBOARD_BIND_ADDR |
127.0.0.1 |
Bind address for the dashboard listener. Use 0.0.0.0 only when running in a container with host-side 127.0.0.1 port mapping in front. |
SP_PROXY_REGION_BIND_ADDR |
127.0.0.1 |
Bind address for the region (data-plane) listeners. Default loopback-only matches the dashboard's pattern -- the proxy reads X-SP-Proxy-Merchant-Id without authentication, so any reachable client can self-claim any merchant identity. Use 0.0.0.0 only in a container with a host-side 127.0.0.1:port mapping, or when direct external access is the operator's deliberate choice. |
SP_PROXY_STRICT_MERCHANT |
false |
When true, requests that supply neither X-SP-Proxy-Merchant-Id nor X-Amz-Access-Token are rejected with 400 instead of being merged into the shared empty-token-hash bucket (where they would share cache + rate-limit state with all other anonymous callers). Recommended for any deployment that does not have a documented anonymous-probe use case. |
Rate Limiting
| Variable | Default | Description |
|---|---|---|
SP_PROXY_RATELIMIT_ENABLED |
true |
Enable rate limiting |
SP_PROXY_RATELIMIT_MODE |
queue |
Default mode: queue, reject, queue-timeout |
SP_PROXY_RATELIMIT_QUEUE_TIMEOUT |
60s |
Queue timeout (for queue-timeout mode) |
SP_PROXY_RATELIMIT_QUEUE_MAX_DEPTH |
100 |
Max queued requests per endpoint |
SP_PROXY_RATELIMIT_THROTTLE_FACTOR |
0.8 |
Safety margin (0.0-1.0) applied to published limits |
SP_PROXY_BUCKET_TTL |
2h |
TTL for idle rate-limit buckets |
Caching
| Variable | Default | Description |
|---|---|---|
SP_PROXY_CACHE_ENABLED |
true |
Enable response caching |
SP_PROXY_CACHE_MAX_MEMORY |
268435456 |
Max cache memory in bytes (256 MB) |
SP_PROXY_CACHE_DEFAULT_TTL |
60s |
Default cache TTL |
SP_PROXY_CACHE_MAX_CLIENT_TTL |
24h |
Upper bound on caller-supplied X-SP-Proxy-Cache-TTL / X-SP-Proxy-Cache-Until headers. The tier default is operator-trusted and is NOT clamped. |
SP_PROXY_CACHE_EXCLUDE_PII |
true |
Exclude PII-containing responses from cache |
Auto-RDT
| Variable | Default | Description |
|---|---|---|
SP_PROXY_RDT_AUTO_MINT |
false |
Enable automatic RDT minting for restricted PII endpoints |
Storage & Retention
| Variable | Default | Description |
|---|---|---|
SP_PROXY_STORAGE_BACKEND |
sqlite |
Metadata store backend |
SP_PROXY_SQLITE_PATH |
/data/sp-proxy.db |
SQLite database path |
SP_PROXY_PURGE_METADATA_RETENTION |
720h |
Request log retention (30 days) |
SP_PROXY_PURGE_AUDIT_RETENTION |
9504h |
Audit log retention (~13 months; DPP §2.6 requires >=12 months) |
Body Storage
Request/response bodies live in hourly JSONL files on a three-tier path: the
active hour is written to local disk (current/), closed files are moved to
the configured backend (recent/), and older files are compressed and moved
again (archive/). Headers live next to the payload in the same JSONL entry.
| Variable | Default | Description |
|---|---|---|
SP_PROXY_BODIES_ENABLED |
true |
Enable request/response body storage |
SP_PROXY_BODIES_PATH |
/data/bodies |
Local body storage root; always holds the active hour |
SP_PROXY_BODIES_BACKEND |
local |
Backend for recent/ and archive/: local or s3 |
SP_PROXY_BODIES_RECENT_MAX_AGE |
24h |
Age at which files leave recent/ for archive/ |
SP_PROXY_BODIES_ARCHIVE_MAX_AGE |
720h |
Age at which files are purged from archive/ (30 days) |
SP_PROXY_BODIES_COMPRESSION |
zstd |
Archive codec: zstd, gzip, or none |
SP_PROXY_BODIES_MAX_CAPTURE_SIZE |
262144 |
Per-message byte cap for captured bodies (256 KiB) |
SP_PROXY_BODIES_MAX_BYTES |
8589934592 |
Hard cap across all tiers (8 GiB); 0 disables size eviction |
[!TIP] Quick sizing rules:
- Dev / CI / single-node:
BODIES_BACKEND=localis enough. SetBODIES_MAX_BYTESto 60-70% of your volume size so the size evictor wins before the filesystem fills up.- Production at ~100k+ req/h: switch to
BODIES_BACKEND=s3. The local disk only needs to hold the active hour plus staging (figure 2-4 GiB for typical SP-API traffic). Everything older lives on the object store.- Storage growth surprise: the active hour is always local. If local disk fills up fast, it's not retention, it's
MAX_CAPTURE_SIZEtimes your req/h. Drop the cap before raising the volume.- DB file not shrinking after purge: SQLite maintenance runs hourly inside the metadata purge job. If the file still grows, check that the job is actually scheduled (look for
metadata purgedlog lines).- PII retention is capped at 30d by default (
BODIES_ARCHIVE_MAX_AGE=720h) per DPP §2.1. Do not raise this above 30d for production traffic that includes PII.
S3 Backend
Only read when SP_PROXY_BODIES_BACKEND=s3. Compatible with AWS S3, Hetzner
Object Storage, MinIO, Cloudflare R2, and any other S3-compatible object store.
| Variable | Default | Description |
|---|---|---|
SP_PROXY_S3_BUCKET |
Bucket name | |
SP_PROXY_S3_REGION |
AWS region (any non-empty value for non-AWS backends) | |
SP_PROXY_S3_ENDPOINT |
Custom endpoint URL; leave blank for AWS S3 | |
SP_PROXY_S3_ACCESS_KEY |
Access key; if blank, the default AWS credential chain is used | |
SP_PROXY_S3_SECRET_KEY |
Secret key | |
SP_PROXY_S3_PATH_STYLE |
false |
Set true for MinIO and some on-prem providers |
[!TIP] Per-provider gotchas:
- AWS S3: on EC2/EKS/ECS, leave
ACCESS_KEY/SECRET_KEYblank and attach an IAM role instead.PATH_STYLE=false.- Hetzner Object Storage: set
REGIONto the datacenter code (fsn1/nbg1/hel1) andENDPOINTtohttps://<code>.your-objectstorage.com.PATH_STYLE=false.- MinIO:
PATH_STYLE=trueis required.REGIONcan be any non-empty value.- Cloudflare R2:
REGION=auto,ENDPOINT=https://<account_id>.r2.cloudflarestorage.com,PATH_STYLE=false.Full setup walkthroughs, IAM policy, compression tuning, sizing tables, and troubleshooting are in docs/STORAGE.md.
Architecture
flowchart TD
Client["Client Application"]
PM["Prometheus Metrics"]
RL["Request Logging"]
MR["Merchant Resolver"]
RDT["Auto-RDT (token swap + cache)"]
Cache["Cache Layer (LRU, 4-tier TTL)"]
Rate["Rate Limiter (Token-bucket per endpoint)"]
PII["PII Redaction (DPP Compliance)"]
RP["Reverse Proxy"]
SPAPI["Amazon SP-API"]
Client --> PM --> RL --> MR --> RDT --> Cache
Cache -- "MISS" --> Rate --> RP --> SPAPI
Cache -- "HIT" --> Client
SPAPI -- "Response" --> PII --> RL --> Client
Middleware Pipeline
Every request passes through the middleware stack in order: Prometheus metrics collection, request/response logging, merchant identity resolution, Auto-RDT token handling (if enabled), cache lookup, rate limiting, and finally the reverse proxy to Amazon. Responses flow back through the same stack, with PII redaction applied before logging and caching.
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Go 1.25, single static binary, no CGO |
| Database | SQLite via modernc.org/sqlite (pure Go) |
| Dashboard | SvelteKit 2, Svelte 5, TailwindCSS |
| Metrics | Prometheus client library |
| Container | Alpine 3.21, multi-stage Docker build |
Caching
Smart Proxy uses a 4-tier LRU in-memory cache designed for SP-API's specific access patterns:
- Automatic TTL management based on endpoint type. Catalog data gets longer TTLs, order data gets shorter ones.
- PII-aware caching. Responses containing PII are excluded from cache by default (
SP_PROXY_CACHE_EXCLUDE_PII=true) to maintain DPP compliance. - Merchant-scoped cache keys. Each seller gets isolated cache entries, preventing data leakage between accounts.
- POST body caching. Read-only POST endpoints (batch queries, fee estimates, shipping rates) are cached using body-hash or per-element strategies. Batch endpoints like
getItemOffersBatchcache each element individually, so overlapping batches share cache entries across requests. - Per-request overrides. Use
X-SP-Proxy-No-Cache,X-SP-Proxy-Cache-TTL, orX-SP-Proxy-Cache-Untilheaders for fine-grained control.
For full details on cache tiers, POST caching strategies, invalidation, and tuning, see docs/CACHING.md.
Rate Limiting
The built-in rate limiter uses per-endpoint token buckets that mirror SP-API's published rate limits:
- Three operating modes:
queue(wait for tokens),reject(return 429 immediately), andqueue-timeout(wait up to N seconds, then 429). - Priority queuing. Mark critical requests as
highpriority viaX-SP-Proxy-Priorityheader to jump the queue. - Throttle factor. Apply a configurable safety margin (default 0.8x) to stay within limits even under burst conditions.
- Stable bucket tracking. When combined with
X-SP-Proxy-Merchant-Id, buckets persist across token rotations instead of resetting every hour.
For the full rate-limit reference and per-endpoint defaults, see docs/RATE_LIMITING.md.
Auto-RDT (Restricted Data Token)
Many SP-API endpoints that return PII (buyer info, shipping addresses, tax data) require a Restricted Data Token instead of a regular access token. Smart Proxy can handle this automatically:
- Automatic detection. The proxy knows which endpoints need RDTs and mints them on-the-fly using the client's LWA token.
- Generic-path caching. One RDT mint covers all requests to the same operation class (e.g., all order GETs) for 55 minutes. Cache hit rate after the first mint approaches 100%.
- Singleflight deduplication. Concurrent requests for the same operation trigger only one upstream mint call.
- Report-aware. The proxy tracks the report lifecycle to determine which document downloads need RDTs and which don't.
- Per-request override. Use
X-SP-Proxy-Force-RDT: trueto force-mint (useful for report documents from notifications) orfalseto skip minting. - Fail-open. If minting fails, the original request is forwarded unchanged.
For the full reference, covered endpoints, and report handling details, see docs/RDT.md.
DPP Compliance & PII Redaction
Smart Proxy includes built-in detection and redaction of personally identifiable information (PII) to help you comply with Amazon's Data Protection Policy (DPP):
- Three redaction modes:
REDACT(replace with placeholder),HASH(one-way SHA-256),OMIT(remove field entirely). - Applied to logs and cache. PII is redacted before data hits SQLite or the request body storage.
- Configurable per field. Define which fields to redact and which mode to use.
For configuration details, field mappings, and compliance guidance, see docs/DPP_COMPLIANCE.md.
Dashboard
Smart Proxy ships with a built-in SvelteKit dashboard on port 9090 for inspecting API traffic:
- Request Log Browser with filtering by merchant, region, endpoint, status code, cache status, HTTP method, latency range, and time range
- Request Detail View with headers, query parameters, and request/response bodies
- Audit Log for system-wide configuration and security events
- Prometheus-compatible
/metricsendpoint for integration with Grafana, Alertmanager, or any Prometheus-based monitoring stack
[!NOTE] The dashboard SPA is embedded into the Go binary at compile time via
go:embed. Runningmake buildautomatically rebuilds the SPA fromweb/before compiling the Go binary.
Development
Local Dev with Docker (recommended)
# Install frontend dependencies (first time only)
cd web && npm install && cd ..
# Build SPA + Docker image and start the container
make dev
This uses docker-compose.dev.yml, which builds the image from your local source instead of pulling from GHCR.
Manual Build
cd web && npm install && cd .. # Frontend deps (first time only)
make build # Build Dashboard SPA + Go binary (embeds the SPA)
./bin/smart-proxy # Run locally
Testing
make test # Unit tests with race detection
make e2e-test # End-to-end tests
make lint # Run golangci-lint
Other Commands
make docker-build # Build Docker image (includes SPA rebuild)
make clean # Clean build artifacts
Documentation
| Document | Description |
|---|---|
| Caching | Cache tiers, TTL strategies, invalidation |
| Rate Limiting | Token buckets, per-endpoint defaults, modes |
| Auto-RDT | Automatic RDT minting, caching, report handling |
| DPP Compliance | PII redaction configuration, field mappings |
| Storage | Body tiers, retention, S3 / Hetzner / MinIO / R2 setup |
| Library Integrations | Guides for popular SP-API client libraries |
Contributing
Contributions are welcome! Bug reports, feature requests, documentation improvements, and code all help make the project better.
Please read CONTRIBUTING.md before submitting a pull request. It covers the development setup, coding conventions, commit message format, and the review process.
If you're looking for a good first contribution, check the issues labeled good first issue.
Community
spiohq.com is an independent community for SP-API developers and integrators. We are not affiliated with Amazon.
Whether you're building a private-label tool, an agency integration, or a full SaaS product on top of SP-API, join us to share knowledge, discuss best practices, and stay ahead of API changes.
Security
Deployment requirements
Before running Smart Proxy with real SP-API traffic:
- Do not expose it to the public internet. Run as a sidecar, on a private VPC subnet, or behind an authenticating reverse proxy. The proxy honors
X-SP-Proxy-Merchant-Idfor tenant identification and has no built-in auth. - Protect the dashboard. Port
9090(default) ships without authentication. Bind it to a private interface or put it behind an auth layer. - Use encrypted volumes. The proxy persists redacted bodies and request metadata to disk in plaintext; rely on EBS/LUKS/dm-crypt for at-rest protection. Tokens (LWA + RDT) live in process memory only and never hit disk.
- Enforce S3 SSE. When using
SP_PROXY_BODIES_BACKEND=s3, setSP_PROXY_S3_SSEand add a deny-unencrypted-put bucket policy. See docs/STORAGE.md#server-side-encryption. - Use
https://for S3 endpoints. Plainhttp://MinIO endpoints leak SigV4 credentials and bodies on the wire. - Consider fail-closed PII. Set
SP_PROXY_PII_FAIL_CLOSED=trueto redact any unmapped SP-API path by default. See docs/DPP_COMPLIANCE.md#fail-closed-mode.
The full checklist lives in SECURITY.md.
Reporting vulnerabilities
If you discover a security vulnerability, please report it responsibly. See SECURITY.md for our disclosure policy and contact information.
Do not open public GitHub issues for security vulnerabilities.
License
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
What this means in practice: You are free to use, modify, and distribute this software. If you modify Smart Proxy and make it available to users over a network (e.g., as part of a SaaS product), you must make your modified source code available under the same license to those users.
Using Smart Proxy as-is (without modification) to proxy your own SP-API requests does not trigger any source-sharing obligations.
For commercial licensing options that don't require AGPL compliance, contact licensing@spiohq.com.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
smart-proxy
command
|
|
|
internal
|
|
|
blob
Package blob defines a minimal object-storage abstraction used by the body storage pipeline.
|
Package blob defines a minimal object-storage abstraction used by the body storage pipeline. |
|
prommetrics
Package prommetrics defines Prometheus metrics for the SP-API proxy.
|
Package prommetrics defines Prometheus metrics for the SP-API proxy. |
|
test
|
|