The Error: Your redirect_uri Looks Right and Still Fails #
You build the eBay consent URL exactly the way you would for any OAuth 2.0 provider, send the seller to it, and get bounced:
The redirect_uri is invalid or does not match the registered value.
So you check your callback URL. It's registered. It's HTTPS. It matches character for character. Nothing is wrong with it — and that's the problem. eBay does not want a URL in the redirect_uri parameter. It wants a RuName.
A RuName (Redirect URL name) is eBay's alias for your callback: an opaque token you register once in the developer portal, which eBay maps internally to the real HTTPS URL. You send the alias; eBay resolves it. It looks nothing like a URL:
redirect_uri=Your_Company-YourAppI-SomeAp-abcdefgh <-- correct (RuName) redirect_uri=https://api.example.com/ebay/callback <-- rejected
That single substitution fixes the majority of "I can't get eBay OAuth working" cases. The rest of this post is the whole flow around it, in the order you'll actually hit each step.
The Flow, End to End #
Four moving parts: your app's keyset, the RuName, the seller's consent, and the tokens you get back.
Step 1: A Production Keyset (and Ignore Auth'n'Auth) #
In the eBay developer portal your application is a keyset, and you get separate sandbox and production keysets. A production keyset gives you three values:
- App ID (also called Client ID)
- Dev ID
- Cert ID (the client secret — treat it like a password)
The portal will also offer you Auth'n'Auth alongside OAuth. Auth'n'Auth is the legacy token scheme for the old Trading API. Unless you specifically need a Trading API call, choose OAuth — it's the modern path, it gives you short-lived access tokens with a refresh token, and every current Sell API is built around it. Mixing the two up costs an afternoon.
One app, many sellers. Unlike Etsy (one app, but per-shop tokens) or Walmart (a credential pair per seller), eBay's model is Amazon-like: one shared keyset for your integration, plus a per-seller refresh token. Design your account table that way from the start.
Step 2: The RuName — and the Legal Address Gate #
You create a RuName in the portal under your keyset's User Tokens section: you supply the real HTTPS callback URL, and eBay hands back the RuName string that stands in for it.
Here's the part that isn't in the API docs, and where a lot of integrations stall: you cannot create a RuName until you have completed eBay's "Confirm your Legal Address" step. The developer account carries a legal entity record, and the address fields on it are often empty even when name, email, phone and company are already filled in. Until that address is saved, the RuName form is blocked.
If you're building for a client, ask for their registered business address early. It's an administrative blocker that can sit for days waiting on someone else, and it gates everything downstream — no RuName means no consent URL, which means no token, which means no orders.
Once you have it, your consent URL looks like this:
GET https://auth.ebay.com/oauth2/authorize
?client_id=<App ID>
&redirect_uri=<RuName> # NOT your callback URL
&response_type=code
&scope=<space-separated scopes>
&state=<random CSRF value>
Sandbox uses auth.sandbox.ebay.com. Note there is no PKCE — this is a plain authorization-code grant, unlike Etsy's OAuth implementation, which requires it. Full parameter reference is in eBay's authorization code grant guide.
Step 3: Scopes — Start Read-Only #
Scopes are locked in at consent time. Adding one later costs you a second trip through the consent screen for every seller you've already connected, so decide deliberately. For a read-only order sync, three are enough:
https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly https://api.ebay.com/oauth/api_scope/sell.account.readonly https://api.ebay.com/oauth/api_scope/commerce.identity.readonly
Writing tracking numbers back later means sell.fulfillment (without .readonly) and a re-authorisation. The full list is in eBay's OAuth scopes reference.
Check the consent screen preview. If a scope isn't enabled on your keyset, eBay doesn't always error loudly — the consent page simply won't list it, and you'll get a token that 403s later. Read the consent screen before you click through it.
Step 4: Exchanging the Code for Tokens #
The callback receives ?code=…&state=…. Verify the state, then exchange. The exchange is form-encoded with HTTP Basic auth — not a JSON body, and not client credentials in the body:
import base64, requests
basic = base64.b64encode(f"{APP_ID}:{CERT_ID}".encode()).decode()
r = requests.post(
"https://api.ebay.com/identity/v1/oauth2/token",
headers={
"Authorization": f"Basic {basic}",
"Content-Type": "application/x-www-form-urlencoded",
},
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": RUNAME, # the RuName again, not the URL
},
timeout=30,
)
tokens = r.json()
Note that redirect_uri is the RuName here too. Sending the real URL at this step is the second place people trip on the same rule.
Token Lifetimes: 18 Months, and It Does Not Rotate #
This is the design-shaping fact. eBay's access token lasts 7200 seconds (2 hours). The refresh token lasts roughly 18 months and does not rotate — refreshing returns a new access token and leaves your refresh token unchanged.
That is the opposite of Etsy, where every refresh issues a new refresh token and invalidates the old one. The practical consequence:
| Marketplace | Refresh token | Re-persist on refresh? |
|---|---|---|
| eBay | ~18 months, static | No — store once at connect |
| Etsy | 90 days, rotates | Yes — atomically, every time |
| Amazon SP-API | Long-lived, static | No |
| Walmart | None (client credentials) | N/A |
So for eBay: persist the refresh token once, cache the access token with its expiry, and re-mint only when you're inside a few minutes of expiry. Also persist refresh_token_expires_in — 18 months is far enough away that nobody remembers it, and when it lapses the seller must go through consent again. Put a warning on your account page at, say, T-30 days. That single field turns a silent outage into a calendar item.
Step 5: Which Seller Just Connected? (Watch the Hostname) #
Never trust the operator to tell you which account they just linked — resolve it from the token itself. The identity call is:
GET https://apiz.ebay.com/commerce/identity/v1/user
That is apiz.ebay.com, not api.ebay.com. The extra z is easy to miss and produces a confusing 404. It returns the username, user ID and registration marketplace, which is exactly what you want to label the connected account with. Reference: getUser.
Step 6: The First getOrders Call #
Orders come from the Sell Fulfillment API with the access token as a normal bearer:
GET https://api.ebay.com/sell/fulfillment/v1/order
Authorization: Bearer <access_token>
Four behaviours worth knowing before you rely on it:
- No filter means the last 90 days. That's the default window, not "everything". For incremental syncs use
filter=lastmodifieddate:[2026-08-01T00:00:00.000Z..]. - Paging is
limit(max 1000, default 50) plusoffset. - One token spans every marketplace the seller trades on. EBAY_US and EBAY_GB orders come back together;
lineItems[].listingMarketplaceIdtells you which is which. cancelStatus.cancelRequestsis always empty on getOrders. It's populated on the single-ordergetOrdercall only. If you're building cancellation handling off the list endpoint, you will see nothing and conclude, wrongly, that no cancellations exist.
Buyer name and full shipping address come back inline for the seller's own orders — there is no separate restricted-data step like Amazon's. If you're coming from SP-API, that's a pleasant surprise; see how Amazon handles the same data for the contrast. Money arrives as a decimal string ({"value": "43.19", "currency": "USD"}), not minor units — parse it, don't divide by 100. Full field list: getOrders reference.
Connecting several seller accounts at once? The auth is only half the job — see how the four big marketplace APIs differ, and how I build multi-account marketplace integrations.
FAQ #
What is a RuName in eBay's API?
A RuName is eBay's redirect-URI alias — an opaque token you register in the developer portal that stands in for your real HTTPS callback URL. You pass it as the redirect_uri parameter in both the consent URL and the token exchange; eBay resolves it to the actual URL internally.
Why does eBay reject my redirect_uri even though it is registered?
Because you are almost certainly sending the URL itself. eBay expects the RuName string in that parameter, not the HTTPS address. Swap in the RuName and the consent URL works.
How long does an eBay refresh token last?
About 18 months (47,304,000 seconds), and it does not rotate — refreshing gives you a new access token but the same refresh token. Store it once, and record its expiry so you can prompt for re-authorisation before it lapses.
Does eBay OAuth require PKCE?
No. eBay uses a plain OAuth 2.0 authorization-code grant with client authentication via HTTP Basic. That differs from Etsy, which does require PKCE with S256.
Why can't I create a RuName in the developer portal?
Usually because the "Confirm your Legal Address" step on your developer account is incomplete. The address fields are frequently empty even when the rest of the profile is filled in, and the RuName form stays blocked until they are saved.
Should I use OAuth or Auth'n'Auth?
OAuth, unless you specifically need the legacy Trading API. Auth'n'Auth is the older token scheme; every current Sell API is designed around OAuth access and refresh tokens.
Related guides
Marketplace APIs
Amazon vs Etsy vs Walmart vs eBay: Four Marketplace APIs, Four Auth Models
If you're integrating more than one marketplace, the authentication layer is where your abstraction breaks first. All four use OAuth 2.0 on paper, and no two behave the same way in practice. Here's what actually differs — token lifetimes, rotation, credential ownership — and how those differences should shape your database schema.
Read guide →Marketplace APIs
Getting Personalisation Text Out of Amazon, Etsy and eBay (Three APIs, Three Answers)
A buyer typed an engraving into a product page and your fulfilment team needs those exact characters. Every marketplace delivers them differently — Amazon hides them in a ZIP behind a signed URL, Etsy puts them inline, and eBay's Fulfillment API doesn't return them at all. Here's where to find them, and the trap in Amazon's payload that silently loses text.
Read guide →Marketplace APIs
No Marketplace Gives You a Real Buyer Phone Number (What to Do Instead)
Your carrier API rejects the shipment because the consignee phone is missing, and the order payload simply doesn't contain one. That's not a bug in your integration — Etsy and Walmart don't return a buyer phone at all, and Amazon's is a relay number. Here's what each marketplace actually gives you and how to satisfy the carrier without inventing data.
Read guide →Don't want to build this yourself?
I set this up for sellers and agencies every week. Book a free 30-minute audit of your Amazon data & PPC setup — you'll leave with a plan either way, whether we work together or not.