eBay Direct Integration
JewelTrak’s eBay integration uses the Inventory API + Account API to push listings directly from inventory data — no CSV uploads, no copy-paste. Item Specifics are pulled live from eBay’s Taxonomy API so the dropdowns stay current with whatever eBay accepts today.
Supports all 5 inventory families: jewelry, watches, diamonds, colored stones, pearls.
Before you start — eBay developer keys
Connecting eBay needs platform-level credentials that you (the JT operator) provision once and add to Vercel env vars. This is a one-time admin task, separate from the per-tenant connect flow.
- Sign in at developer.ebay.com (or create a free account)
- My Account → Application Keysets → Create a keyset
- eBay issues a Sandbox keyset + a Production keyset. Each has:
- App ID (Client ID)
- Cert ID (Client Secret)
- For each keyset, User Tokens → Add eBay Redirect URL (RuName):
- Redirect URL:
https://app.jeweltrak.com/settings/integrations/ebay/callback - Privacy policy URL: your privacy policy
- Redirect URL:
- Save. eBay generates a RuName string like
JewelTrak-JewelTrak-PRD-xxxxxxxxx. - Paste into Vercel environment variables:
EBAY_CLIENT_ID(App ID)EBAY_CLIENT_SECRET(Cert ID)EBAY_RU_NAME(RuName string)EBAY_SANDBOX(truefor sandbox testing,falsefor production)
Tenants see an amber “eBay developer keys pending” notice on the integration card until these env vars are populated. The Connect button is disabled until then.
Connecting eBay (tenant flow)
Once the keys are in place:
- Settings → Integrations → eBay → Connect eBay
- You’re redirected to eBay to log in with the seller account and grant JewelTrak permission to manage inventory and orders
- eBay bounces back to JewelTrak with a success banner
- Token + refresh token are saved server-side; access tokens auto-refresh in the background (eBay access tokens last ~2 hours, refresh tokens last 18 months)
Post-connect setup — Business Policies + fulfillment location
eBay’s Inventory API requires every listing to reference:
- A Shipping Policy ID
- A Payment Policy ID
- A Return Policy ID
- A Merchant Location Key (your fulfillment address)
Click Load my eBay choices on the integration card. JT pulls these from your eBay account in one request and populates dropdowns. Pick one of each and Save eBay Config.
If any are missing, JT shows the per-item Publish button as disabled with a “finish setup” link.
Per-item listing — the eBay sub-tab
On any inventory item (jewelry/watch/diamond/colored stone/pearl), open Sales Channels → eBay sub-tab.
The form has:
- Category picker — driven by JT’s seeded jewelry categories + lazy-fetched from eBay’s Taxonomy API as you pick. Category ID drives which Item Specifics show below.
- Title — eBay’s 80-char hard cap, with a live remaining-character counter.
- Subtitle — 55-char cap. eBay charges $1.50 to use this (warned in the help text).
- Format — Fixed Price or Auction. Auction unlocks Starting Bid field.
- Buy It Now price — required for Fixed Price; the floor for Auction.
- Quantity — defaults to 1 (single-unique jewelry is the default model).
- Duration — Good ‘Til Cancelled for Fixed Price; 3/5/7/10 days for Auction.
- Best Offer — accept yes/no, plus optional auto-accept and minimum thresholds.
- Condition + Condition Description — required.
- Description — HTML body. Falls back to the inventory item Description if left blank.
- Item Specifics — driven dynamically by the chosen category. Required, recommended, and optional aspects each render with the right control (dropdown for selection-only, text input for free-text, etc.).
Save the form to persist the data without publishing. Publish to eBay posts the listing live; the panel updates with the listing ID and timestamp.
Item Specifics — Taxonomy cache
eBay’s Item Specifics aren’t static — they change as eBay adds new aspects or refines category structures. JT handles this with a nightly cache refresh at 03:30 UTC:
- Every (Marketplace, Category) pair you’ve used or that’s older than 7 days gets a fresh fetch from eBay’s
/commerce/taxonomy/v1/get_item_aspects_for_categoryendpoint - Stored in the per-tenant
ebay_category_aspectstable as opaque JSONB - The listing panel reads the cache at render time — no live API call per page load
Until you connect a real eBay account, JT seeds 9 default leaf categories (5 jewelry, 1 watch, 3 stone) with a baseline of aspects so the panel works for testing.
Push flow (under the hood)
When you click Publish to eBay the handler does 3 sequential API calls:
createOrReplaceInventoryItem— PUT to eBay with title, description, aspects, image URLs, condition, quantity. Idempotent by SKU.createOffer— POST creating the offer record (category, price, business policies, fulfillment location).publishOffer— POST that turns the draft offer into a live listing. Returns the eBay listing ID.
Both successes and failures flow back into channel_listings. Failures stamp the LastError so the panel shows what went wrong on the next page render.
Auto-retire on sale
When JewelTrak inventory drops to QOH=0 (POS sale, void, scrap, memo issue, manual edit), the dispatcher fans out to every connected channel. For eBay this means the listing is withdrawn automatically. No more “available” listings for items already sold.
Re-listing on 0→1 is NOT automated — operator re-publishes manually to re-check pricing + description.
Bulk push — coming Phase 1b
Per-item Publish is live today. Bulk publish from the inventory/watches/diamonds list pages is on the Phase 1b list.
Sandbox vs production
EBAY_SANDBOX=true routes auth + API calls to sandbox.ebay.com. Test there until your workflow is solid, then flip to production by:
- Setting
EBAY_SANDBOX=false - Swapping in Production credentials (a different App ID / Cert ID / RuName from the Production keyset)
- Redeploying Vercel
Each tenant must re-connect on the new environment — sandbox and production OAuth tokens are not interchangeable.
Phase 1b roadmap
Items shipping after the first round of live tenant testing:
- Quantity sync into the dispatcher (helper exists, not yet wired)
- Bulk publish + bulk retire from list pages
- Description auto-derivation from inventory fields when BodyHtml is blank
- Frooition MFL import path (parse a tenant’s existing Frooition exports into ebay_settings)
Phase 2 — inbound orders
Not yet built. When a sale happens on eBay (vs in JT), eBay sends an order webhook that JT will turn into an invoice with SourceChannel='ebay'. The fulfillment-on-finalize trigger then pushes shipment back to eBay so the buyer sees Fulfilled status. Same pattern Shopify uses today.
Troubleshooting
- Connect eBay button is disabled / “developer keys pending” — Vercel env vars not populated. Admin task; one-time setup.
- Token exchange fails after eBay redirect — Sandbox vs Production mismatch. Check
EBAY_SANDBOXenv var against the keyset’s environment. - Publish button is disabled — One of: not connected, business policies not selected, fulfillment location not chosen, required Item Specifics empty. Hover the disabled button for the specific reason.
- “Category aspects not loaded” — Cache miss for a brand-new category you just picked. Hit save once and refresh — the lazy fetch + cache populate happens on save.
- Listing publishes but Item Specifics look incomplete — eBay’s Taxonomy API changed; cache is stale. Wait until next cron run (03:30 UTC daily) or force a refresh via the admin tools.
- “Authorization required” on listing changes — Your eBay refresh token expired (18-month window). Re-Connect on the integration card to issue a new one.