eBay

eBay Sell APIs: OAuth 2.0 user tokens, Fulfillment, Inventory and Finances REST plus legacy Trading XML. Reads orders and payouts, writes price and stock.

eBay is the oldest general marketplace still operating at scale and the one with the deepest API history, which is also its main complication: there are two complete generations of seller API in force at once. The modern REST surface, grouped under sell, commerce and buy, covers orders, inventory, offers, finances, feeds and notifications. Underneath it the XML Trading API from 2001 is still live, still supported, and still the only route to several things the REST APIs do not expose, notably full legacy listing management and some returns flows. A serious eBay connector uses both. Everything below is the seller side; the Buy APIs, which serve buying applications, are a separate programme with separate approval.

At a glance

Warning

developer.ebay.com blocked every request from our fetcher with HTTP 403, including the OAuth guide, the REST request components page and the Fulfillment reference. Every path, model and scope on this page therefore comes from a mirror: SDKs generated directly from eBay's published OpenAPI contracts, and eBay's own event-notification-nodejs-sdk. They agree with each other, but sample response bodies below are assembled from the model definitions rather than copied from eBay's own examples. Confirm against the live reference before implementing.

What it is

eBay Inc. runs marketplaces in around 20 countries with roughly 130 million active buyers and gross merchandise volume in the region of 75 billion US dollars a year. It is an auction marketplace by origin but the overwhelming majority of volume is now fixed price. Sellers range from individuals clearing a loft to large retail brands running official outlet stores, and there is no application queue: anyone with a verified account and a payment method can list.

Structurally eBay differs from Amazon and Walmart in a way that matters to the data model. There is no enforced shared catalogue. A seller's listing is its own object with its own title, images and item specifics, identified by an itemId in the legacy world and by an offerId plus a listingId in the modern Inventory API world. Catalogue matching exists and is encouraged but is not mandatory outside a few categories, so eBay listings carry far more seller authored content than a Walmart or Amazon offer.

Each national site is a separate marketplace identified by a marketplace id such as EBAY_US, EBAY_GB, EBAY_DE or EBAY_AU, and a single seller account can list on several of them. eBay closed its India marketplace in 2018 after selling its stake to Flipkart, so an Indian brand on eBay is exporting into the US, UK, Germany or Australia marketplaces rather than selling domestically. For our database that means an eBay connection is one account but potentially several marketplaces, and the marketplace id has to be carried on the listing row, not just on the account row. eBay's payments have been managed by eBay itself since the PayPal separation completed in 2021, which is why there is a proper Finances API with payouts and per transaction fee detail.

API access

  1. Register at developer.ebay.com. Registration is immediate and free.
  2. A sandbox keyset is issued straight away. Each keyset is four values: an App ID, which is also the OAuth client_id; a Dev ID; a Cert ID, which is also the OAuth client_secret; and one or more RuNames.
  3. A production keyset requires completing the application form describing the integration. Approval is normally quick for a straightforward seller tool.
  4. A RuName, eBay's redirect URL name, is a token that stands in for a redirect URI. It is configured in the developer console against an actual HTTPS URL, and OAuth calls pass the RuName in the redirect_uri parameter rather than the URL itself. This trips up every first implementation.
  5. Some legacy Trading API calls, and higher call volumes, require passing the Compatible Application Check, an eBay review of the application against its policies.

Versions in force: the sell REST APIs are versioned in the path, currently v1 for Fulfillment, Inventory, Account, Feed and Finances, with some APIs carrying a _beta suffix. The Trading API is versioned by a compatibility level sent per call in X-EBAY-API-COMPATIBILITY-LEVEL, historically in the 1100 to 1300 range; a call that omits it gets very old behaviour. eBay publishes deprecation notices per call rather than retiring whole versions, and legacy Trading calls have been deprecated piecemeal over several years with long lead times.

Official SDKs: eBay publishes OAuth client libraries for Node, Python and Java, the event-notification SDKs for Node, Java, Python and PHP, and OpenAPI contracts for every REST API which generate clients in any language. The community SDKs on GitHub used as sources for this page are themselves generated from those contracts.

Base URLs: REST production is https://api.ebay.com, except Finances which is on https://apiz.ebay.com; REST sandbox is https://api.sandbox.ebay.com; the Trading API is https://api.ebay.com/ws/api.dll and https://api.sandbox.ebay.com/ws/api.dll. The Finances API sitting on a different host is a real and easily missed difference: a client configured with a single base URL will 404 against every payout call.

Authentication

There are two token types and the difference is not cosmetic. An application token identifies the application only and can reach public data. A user token identifies a seller as well and is required for every sell endpoint. Both are bearer tokens.

Application token, client credentials grant

curl -X POST "https://api.ebay.com/identity/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Authorization: Basic $(printf '%s' "$CLIENT_ID:$CLIENT_SECRET" | base64)" \
  -d "grant_type=client_credentials" \
  -d "scope=https://api.ebay.com/oauth/api_scope"
{ "access_token": "v^1.1#i^1#f^0#r^0#I^3#p^3#t^H4sIAAAA...", "expires_in": 7200, "token_type": "Application Access Token" }

User token, authorization code grant

  1. Send the seller to the consent screen. redirect_uri is the RuName, not a URL.
GET https://auth.ebay.com/oauth2/authorize
  ?client_id={App ID}
  &redirect_uri={RuName}
  &response_type=code
  &scope=https://api.ebay.com/oauth/api_scope/sell.fulfillment%20https://api.ebay.com/oauth/api_scope/sell.inventory%20https://api.ebay.com/oauth/api_scope/sell.finances
  &state={nonce}
  1. eBay redirects to the URL behind the RuName with code and state. The code is short lived, a few minutes.

  2. Exchange it. Note that redirect_uri must be the RuName again and must match exactly.

curl -X POST "https://api.ebay.com/identity/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Authorization: Basic $(printf '%s' "$CLIENT_ID:$CLIENT_SECRET" | base64)" \
  -d "grant_type=authorization_code" \
  -d "code=v^1.1%23i^1%23..." \
  -d "redirect_uri=Our_Company-OurComp-conn-abcdefg"
{
  "access_token": "v^1.1#i^1#r^0#p^3#I^3#f^0#t^H4sIAAAAAAAAAOVYfWwbZ...",
  "expires_in": 7200,
  "refresh_token": "v^1.1#i^1#f^0#r^1#I^3#p^3#t^Ux4MjoxRDU1RDJBODE4...",
  "refresh_token_expires_in": 47304000,
  "token_type": "User Access Token"
}

Lifetimes: the access token is 7,200 seconds, that is 2 hours. The refresh token is 47,304,000 seconds, that is 18 months. Those two numbers define the whole credential design. A connector refreshes several times a day per seller, and re-consents every eighteen months, so the credential store needs a scheduled expiry alert at, say, seventeen months, plus a self-service reconnect page. A seller who changes their eBay password or revokes application access invalidates the refresh token immediately, so refresh failures must be surfaced to the seller rather than retried.

  1. Refresh with the same endpoint and grant_type=refresh_token, passing the refresh token and repeating the scopes, which must be a subset of those originally granted.

Scopes

Scopes are full URLs under https://api.ebay.com/oauth/api_scope, space separated in the token request and URL encoded in the authorize link. Confirmed in the generated SDK contracts on 2026-09-21: the bare api_scope is the base scope for public data used with the client credentials grant; sell.fulfillment reads and writes orders and shipping fulfilments with a .readonly variant; sell.inventory manages inventory items and offers, again with a .readonly variant; sell.finances views payments and order information and initiates refunds; sell.payment.dispute manages payment disputes; sell.analytics.readonly reads seller performance reports. Ask for the minimum set, because eBay shows the seller a plain language list of what is being requested and a long list depresses connection rates.

An authenticated call

curl -X GET "https://api.ebay.com/sell/fulfillment/v1/order?limit=50&filter=lastmodifieddate:%5B2026-09-20T00:00:00.000Z..%5D" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Accept: application/json" \
  -H "X-EBAY-C-MARKETPLACE-ID: EBAY_US"

X-EBAY-C-MARKETPLACE-ID selects the marketplace and is required on most Inventory and Account calls; Fulfillment calls generally infer it from the order. Content-Language is required on calls that create listing content, for example en-US, and omitting it is a common cause of an otherwise unexplained 400.

Legacy Trading API authentication

The Trading API predates OAuth and originally used an Auth'n'Auth token. It now accepts the same OAuth user access token, passed in a different header.

POST https://api.ebay.com/ws/api.dll
X-EBAY-API-CALL-NAME: GetSellerList
X-EBAY-API-SITEID: 0
X-EBAY-API-COMPATIBILITY-LEVEL: 1193
X-EBAY-API-IAF-TOKEN: {user access token}
Content-Type: text/xml

X-EBAY-API-SITEID is the numeric site identifier, 0 for the US, 3 for the UK, 77 for Germany, 15 for Australia. It is a different identifier space from the REST marketplace ids, so a connector needs a mapping table between EBAY_US and 0.

Multi account

One application keyset serves every seller. Each seller has its own refresh token, and the token alone determines which seller's data is returned; there is no account parameter. A seller selling on three marketplaces is still one token, and the marketplace is chosen per call by header.

Objects we can read

Orders

Sell Fulfillment API, base path https://api.ebay.com/sell/fulfillment/v1.

GET /order parameters, from the generated contract: filter, limit (default 50, maximum 1000), offset (default 0) and orderIds (up to 50 comma separated ids). When orderIds is supplied every other parameter is ignored.

The filter parameter uses eBay's bracket range syntax: creationdate:[2026-09-01T00:00:00.000Z..2026-09-21T00:00:00.000Z], lastmodifieddate:[2026-09-20T00:00:00.000Z..], and orderfulfillmentstatus:{NOT_STARTED|IN_PROGRESS} or orderfulfillmentstatus:{FULFILLED|IN_PROGRESS}.

Warning

The generated contract states plainly that filter "returns data from only the last three months". An eBay backfill cannot be done through GET /order alone. For history beyond three months use the legacy Trading GetOrders call, which reaches further back, or the Feed API order report. This is the single biggest planning constraint on an eBay connector.

Pagination is offset based, with href, next, limit, offset and total on the returned OrderSearchPagedCollection. Offset pagination over a moving result set is unsafe, so page by a fixed lastmodifieddate window and sort within it rather than walking a deep offset.

Order model properties, confirmed from the generated contract:

{
  "orderId": "12-09134-88765",
  "legacyOrderId": "220134567890-2345678901",
  "creationDate": "2026-09-20T11:02:41.000Z",
  "lastModifiedDate": "2026-09-20T15:48:03.000Z",
  "orderFulfillmentStatus": "NOT_STARTED",
  "orderPaymentStatus": "PAID",
  "sellerId": "example_seller",
  "salesRecordReference": "8842",
  "ebayCollectAndRemitTax": true,
  "buyer": { "username": "example_buyer" },
  "pricingSummary": {
    "priceSubtotal": { "value": "42.00", "currency": "USD" },
    "priceDiscount": { "value": "2.00", "currency": "USD" },
    "deliveryCost": { "value": "5.95", "currency": "USD" },
    "tax": { "value": "3.55", "currency": "USD" },
    "total": { "value": "49.50", "currency": "USD" }
  },
  "paymentSummary": {
    "totalDueSeller": { "value": "49.50", "currency": "USD" },
    "payments": [
      { "paymentMethod": "CREDIT_CARD", "paymentStatus": "PAID", "paymentDate": "2026-09-20T11:02:55.000Z" }
    ]
  },
  "cancelStatus": { "cancelState": "NONE_REQUESTED" },
  "fulfillmentStartInstructions": [
    {
      "fulfillmentInstructionsType": "SELLER_DEFINED",
      "minEstimatedDeliveryDate": "2026-09-24T07:00:00.000Z",
      "maxEstimatedDeliveryDate": "2026-09-26T07:00:00.000Z",
      "shippingStep": {
        "shipTo": {
          "fullName": "Jane Doe",
          "contactAddress": {
            "addressLine1": "123 Main St",
            "city": "Brooklyn",
            "stateOrProvince": "NY",
            "postalCode": "11205",
            "countryCode": "US"
          },
          "primaryPhone": { "phoneNumber": "3155554444" },
          "email": "e7f2a1@members.ebay.com"
        },
        "shippingCarrierCode": "USPS",
        "shippingServiceCode": "USPSPriority"
      }
    }
  ],
  "lineItems": []
}

Field names above are taken from the published Order, PricingSummary and ShippingFulfillment model definitions. The values are illustrative; eBay's own sample responses were not reachable.

Points for the loader:

  • Two identifiers per order. orderId is the REST format, legacyOrderId is the Trading API format. Store both, because notifications, the Trading API and Seller Hub all speak the legacy form. Timestamps are ISO 8601 in UTC with milliseconds.
  • orderFulfillmentStatus and orderPaymentStatus are two independent axes. An order can be PAID and NOT_STARTED, or FULFILLED and PAID, and a refund moves the payment status without touching fulfilment.
  • pricingSummary gives a real order level total. ebayCollectAndRemitTax true means eBay collected the sales tax and will remit it, so that money is not the seller's revenue and must not be counted as such.
  • The buyer's email in shippingStep.shipTo.email is an eBay relay address, not the real one. The postal address and phone are real PII.

Order items

lineItems[] on the order. Confirmed properties: lineItemId, legacyItemId, legacyVariationId, sku, title, quantity, lineItemCost, discountedLineItemCost, total, deliveryCost, appliedPromotions[], taxes[], ebayCollectAndRemitTaxes[], refunds[], soldFormat, lineItemFulfillmentStatus, listingMarketplaceId, purchaseMarketplaceId, giftDetails, properties, lineItemFulfillmentInstructions.

{
  "lineItemId": "10040567890123",
  "legacyItemId": "220134567890",
  "legacyVariationId": "450987654321",
  "sku": "TSHIRT-BLK-M",
  "title": "Organic Cotton T Shirt, Black, Medium",
  "lineItemCost": { "value": "42.00", "currency": "USD" },
  "quantity": 1,
  "soldFormat": "FIXED_PRICE",
  "listingMarketplaceId": "EBAY_US",
  "purchaseMarketplaceId": "EBAY_US",
  "lineItemFulfillmentStatus": "NOT_STARTED",
  "total": { "value": "45.55", "currency": "USD" },
  "deliveryCost": { "shippingCost": { "value": "5.95", "currency": "USD" } },
  "discountedLineItemCost": { "value": "40.00", "currency": "USD" },
  "appliedPromotions": [
    { "promotionId": "5555555555", "discountAmount": { "value": "2.00", "currency": "USD" } }
  ],
  "taxes": [
    { "taxType": "STATE_SALES_TAX", "amount": { "value": "3.55", "currency": "USD" } }
  ],
  "refunds": []
}

Note that sku can be absent. eBay does not require a seller SKU on a listing, so a connector must fall back to legacyItemId plus legacyVariationId as the join key and treat missing SKUs as a data quality signal to raise with the seller. listingMarketplaceId and purchaseMarketplaceId can differ, which is how cross-border purchases show up.

Products and listings

Two mechanisms, and which one applies depends on how the seller created the listing.

Sell Inventory API, base path https://api.ebay.com/sell/inventory/v1. This is the modern model: an inventory item keyed by SKU, and one offer per marketplace against that SKU, which when published becomes a listing.

GET /offer requires the sku parameter and takes marketplace_id, format, limit and offset. The offer carries the price, quantity and listing status; the inventory item carries the product content. The published listing id appears on the offer once published.

{
  "offers": [
    {
      "offerId": "8794567890",
      "sku": "TSHIRT-BLK-M",
      "marketplaceId": "EBAY_US",
      "format": "FIXED_PRICE",
      "availableQuantity": 24,
      "categoryId": "15687",
      "merchantLocationKey": "WAREHOUSE_MUMBAI",
      "status": "PUBLISHED",
      "pricingSummary": { "price": { "value": "42.00", "currency": "USD" } },
      "listing": { "listingId": "220134567890", "listingStatus": "ACTIVE", "soldQuantity": 112 },
      "listingPolicies": {
        "fulfillmentPolicyId": "6000000001",
        "paymentPolicyId": "6000000002",
        "returnPolicyId": "6000000003"
      }
    }
  ],
  "total": 1,
  "limit": 25,
  "offset": 0
}

The exact offer field list is medium confidence: the Offer model document was not reachable in the mirror, so the shape above is reconstructed from the endpoint set and long standing published behaviour. Verify before mapping.

Legacy Trading API. Listings created in Seller Hub, in the classic listing flow, or by older tools are not in the inventory model and will not appear under GET /inventory_item. Reading those requires GetSellerList or GetMyeBaySelling, and GetItem for the detail of one listing. bulk_migrate_listing converts eligible legacy listings into the inventory model, but it is one way and not eligible for every listing type.

Note

Plan for both. A connector that only reads the Inventory API will silently report zero listings for a seller whose entire catalogue predates it, which is a large share of long established eBay sellers. Read GET /inventory_item and GetSellerList, then reconcile on legacyItemId.

Inventory

Quantity lives in two places depending on the listing model. In the inventory model it is availability.shipToLocationAvailability.quantity on the inventory item, and availableQuantity on the offer. In the legacy model it is the listing's Quantity less QuantitySold, read through GetItem or GetSellerList.

The inventory item also supports per location availability through availabilityDistributions, keyed by merchantLocationKey, which is how a multi warehouse seller reports stock per site. There is no reserved quantity concept exposed, so inventory.quantity_reserved stays empty.

Shipments and tracking

GET /order/{orderId}/shipping_fulfillment returns the fulfilment records created for an order. Confirmed model properties:

{
  "fulfillments": [
    {
      "fulfillmentId": "9405509699937003457459",
      "shipmentTrackingNumber": "9405509699937003457459",
      "shippingCarrierCode": "USPS",
      "shippedDate": "2026-09-21T09:14:00.000Z",
      "lineItems": [
        { "lineItemId": "10040567890123", "quantity": 1 }
      ]
    }
  ],
  "total": 1
}

That is the whole model: five fields. There are no carrier scan events, no delivery timestamp and no shipping cost. Delivery is inferred from the order's fulfilment status, and per event tracking must come from a carrier connector. shippingCarrierCode uses eBay's own carrier code list, which differs per marketplace, so it needs normalising against our carrier table.

Returns and cancellations

Returns are not in the Sell REST family. They live in the Post-Order API v2, a separate surface at https://api.ebay.com/post-order/v2/, covering return search, return detail, return decisions and cancellations. It authenticates with the same OAuth user token but expects it in an Authorization: IAF {token} header rather than Bearer. Confidence low to medium: the Post-Order reference was not reachable, and eBay has signalled for some time that this functionality will move to a modern Sell Returns API. Verify current status before building.

What is reachable in the modern APIs:

  • cancelStatus on the order, with cancelState and a cancelRequests array, covers buyer cancellation requests.
  • refunds[] on each line item records partial and full refunds.
  • POST /order/{order_id}/issue_refund issues a refund.
  • Payment disputes, which are the eBay money back guarantee cases, have their own endpoints under Sell Fulfillment and their own scope.

Payments and settlements

Sell Finances API, base path https://apiz.ebay.com/sell/finances/v1. This is the strongest settlement surface of any marketplace in this catalogue.

A payout is the money that lands in the seller's bank account. A transaction is a single money movement: a sale, a refund, a fee, a credit, a dispute, a shipping label charge. Transaction model properties, confirmed:

{
  "transactions": [
    {
      "transactionId": "5000123456",
      "transactionDate": "2026-09-20T11:03:12.000Z",
      "transactionType": "SALE",
      "transactionStatus": "FUNDS_ON_HOLD",
      "bookingEntry": "CREDIT",
      "amount": { "value": "49.50", "currency": "USD" },
      "orderId": "12-09134-88765",
      "payoutId": "5000987654",
      "paymentsEntity": "EBAY_INC",
      "totalFeeBasisAmount": { "value": "49.50", "currency": "USD" },
      "totalFeeAmount": { "value": "6.53", "currency": "USD" },
      "orderLineItems": [
        {
          "lineItemId": "10040567890123",
          "marketplaceFees": [
            { "feeType": "FINAL_VALUE_FEE", "amount": { "value": "6.53", "currency": "USD" } }
          ]
        }
      ],
      "buyer": { "username": "example_buyer" }
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}

bookingEntry of CREDIT or DEBIT gives the sign, transactionType gives the category, and orderLineItems[].marketplaceFees[] gives the per line fee breakdown joined to the order line. That combination maps almost exactly onto our settlements table, including fees[] {type, amount}, which is rare.

transactionDate supports a filter, and payoutId lets us group transactions into the payout that actually paid them, which is how a reconciliation report is built.

Customers

No customer endpoint. What exists is buyer.username on the order and on each transaction, plus the relay email and the shipping address. buyer.username is a stable identifier across orders for the same buyer, which makes eBay better than Walmart for repeat purchase analysis and still short of a real customer record. Treat username, address, phone and relay email all as PII.

Locations

Sell Inventory API, GET /location and GET /location/{merchantLocationKey}, plus create, update, enable and disable. A merchant location has a key chosen by the seller, an address, a location type and operating hours. merchantLocationKey is referenced on offers and on per location inventory, so this maps cleanly onto our locations table with channel_location_id equal to the key.

Writing back: listings, price and stock

Price and stock, inventory model

POST /sell/inventory/v1/bulk_update_price_quantity is the workhorse. It updates price on the offer and quantity on the inventory item in one call, per SKU, across marketplaces.

{
  "requests": [
    {
      "sku": "TSHIRT-BLK-M",
      "shipToLocationAvailability": { "quantity": 24 },
      "offers": [
        { "offerId": "8794567890", "availableQuantity": 24, "price": { "value": "39.00", "currency": "USD" } }
      ]
    }
  ]
}

The response is synchronous and returns a per SKU status with any errors attached, so no polling is needed. eBay caps the bulk inventory calls at 25 records per request; batch accordingly. bulk_create_or_replace_inventory_item and bulk_get_inventory_item carry the same cap.

For a single SKU, PUT /inventory_item/{sku} replaces the whole inventory item, including availability, so a partial write will erase fields not sent. Read, merge, write, or use the bulk price and quantity call which is genuinely partial.

Price and stock, legacy listings

ReviseInventoryStatus in the Trading API is the equivalent for listings not in the inventory model. It updates price and quantity for up to four listings or variations per call, which is why legacy catalogues are slow to sync. ReviseFixedPriceItem handles wider content changes on a single listing.

Listing content

Creating a listing in the inventory model is three steps: PUT /inventory_item/{sku} with the product content, POST /offer with the price, quantity, category and business policies, then POST /offer/{offerId}/publish/. Business policies, that is fulfilment, payment and return policies, are created first through the Sell Account API and referenced by id; a publish will fail without them. POST /offer/get_listing_fees returns the fees a publish would incur before committing, which is worth calling for high value listings.

Feed API, for large batches

The Sell Feed API, base path https://api.ebay.com/sell/feed/v1, handles volumes beyond what the synchronous bulk calls can carry. The model is a task: create a task with a feed type, upload a file against it, poll the task status, then download the result file. There are dedicated task resources for order reports and for inventory, plus recurring schedules. Confidence medium: the Feed reference and its OpenAPI contract were not reachable on 2026-09-21, so confirm the exact resource paths, the feed type enumeration and the file format, which is a tab separated flat file rather than JSON. The Feed API's order report is also the practical answer to the three month limit on GET /order, and is worth confirming early if historical backfill matters.

Webhooks and notifications

eBay's Notification API lives under https://api.ebay.com/commerce/notification/v1. The model has four objects: a topic, which is the event type; a destination, which is our HTTPS endpoint; a subscription, which binds a topic to a destination; and a configuration on the destination holding the endpoint and the verification token.

Endpoint validation handshake

Before eBay will send events, it validates the destination. It sends a challenge code, and the endpoint must respond with a hash computed over the challenge code, the verification token we configured, and the endpoint URL. The verification token is ours, chosen at destination creation, and must match what eBay holds. eBay's own event-notification-nodejs-sdk implements this and describes the requirement as a verificationToken unique to the endpoint that must match eBay's records.

Signature verification

Every delivered notification carries an X-EBAY-SIGNATURE header containing a base64 encoded structure with a key id and an elliptic curve signature over the message body. Verification, as implemented in eBay's SDK:

  1. Decode X-EBAY-SIGNATURE and read the key id out of it.
  2. Fetch the matching public key from GET /commerce/notification/v1/public_key/{public_key_id}, using an application access token.
  3. Cache the key. The SDK uses a least recently used cache, because the key rotates rarely and re-fetching per message is wasteful.
  4. Verify the signature over the raw body. Verify before parsing, and verify against the exact bytes received, not a re-serialised object.
  5. Respond 204 No Content on success. eBay's SDK returns 412 Precondition Failed on a verification failure, which is the convention to follow.

Topics

The only topic confirmed in eBay's own SDK repository is MARKETPLACE_ACCOUNT_DELETION, which is the mandatory one: every production application that stores eBay user data must implement it and delete that user's data on receipt. It is a compliance requirement, not an optimisation. The SDK describes itself as generic for any topic, and eBay publishes further seller topics covering order and listing events, but that catalogue could not be read on 2026-09-21, so confidence on the seller event topic list is low. Build the connector to poll and treat notifications as an accelerator: GET /order filtered on lastmodifieddate every 10 to 15 minutes, GET /offer per SKU or GetSellerList nightly, GET /transaction filtered on transactionDate hourly, GET /payout daily.

Rate limits and pagination

eBay's model is a daily call allowance per application per API, not a per second throttle. The default grant for a new production application is 5,000 calls per day for most APIs, raised by application through the developer console and the Compatible Application Check. The allowance resets on a fixed daily cycle in Pacific time.

The application's actual entitlement and current consumption are readable at runtime through the Developer Analytics API, GET /developer/analytics/v1_beta/rate_limit, with a user_rate_limit variant for per user limits, under a developer analytics read only scope. The response groups by API context, API name and version, then by resource, with a limit, a remaining count, a reset time and a time window per resource. Confidence medium: this endpoint could not be read on 2026-09-21 and the exact response shape should be confirmed. Practical consequences: 5,000 calls a day is tight, so a seller with 5,000 listings cannot be synced one call per listing per day. Use bulk_get_inventory_item at 25 records a call, the Feed API for whole catalogue pulls, and reserve single calls for deltas. The limit is per application rather than per seller, so a connector serving many sellers shares one pool, which is the opposite of Walmart and means the call budget must be scheduled centrally rather than per connection. Call the rate limit endpoint at the start of each run and shed load when remaining is low, rather than discovering the ceiling through errors. The Trading API has its own separate daily allowance of the same order of magnitude.

Pagination: limit and offset throughout the REST APIs, with total, href and next on the collection. Fulfillment GET /order allows a limit of up to 1000, Inventory collections default to 25 and are capped lower. Deep offsets over a changing result set drop and duplicate records, so window by date and keep each window small enough to fit one or two pages.

Mapping to the unified model

orders

order_items

listings

inventory

shipments

settlements

returns and customers

returns.* maps onto Post-Order API v2 return objects, which are outside the Sell REST family and low confidence here. returns.refund_amount can be taken from lineItems[].refunds[] on the order without the Post-Order API, but that covers the refund only, not the return case. For customers, customers.customer_id is buyer.username, customers.email is the masked relay address on shipTo.email, and customers.addresses[] is shipTo.contactAddress, which is real PII.

Gaps and open questions

  • Nothing on this page was read from developer.ebay.com, which returned 403 to every request. The mirrors used are generated from eBay's own OpenAPI contracts and agree with each other, but sample response bodies are reconstructed from model definitions and the whole page should be re-verified against the live reference before implementation.
  • The three month window on GET /order filters is confirmed in the contract but the exact workaround is not. Whether the Feed API order report, the Trading GetOrders call, or both, is the supported route to a full history backfill needs confirming. The Sell Feed API resource paths, feed type enumeration and file format were also not reachable.
  • The seller facing notification topic list was not reachable. Only MARKETPLACE_ACCOUNT_DELETION is confirmed. Whether a usable order created topic exists determines whether the connector can drop below a ten minute poll.
  • The Developer Analytics rate limit response shape, and whether the default daily allowance is still 5,000 calls per API in 2026, need confirming from the live console.
  • The Post-Order API's current status is the largest open question. It is the only route to return objects, it uses a different authentication header, and eBay has signalled a replacement for some time.
  • The Offer model field list, particularly the strike through and list price fields, is reconstructed rather than read. Separately, eBay marketplace ids and Trading API site ids are two different identifier spaces for the same concept and the mapping table has to be built and maintained by hand.

Sources

Official, all of which returned HTTP 403 to our fetcher on 2026-09-21 and are listed for the human reader: eBay Developer Program, the OAuth tokens guide, the Sell Fulfillment getOrders reference and REST request components.

Read successfully, all on 2026-09-21: