Phoneo Partner API

Read-only inventory feed for partner storefronts · v1 · Updated 2026-09-24

Base URLhttps://super.phoneo.in/api/partner/v1
On this page

What this API is

Phoneo gives your storefront a live, read-only feed of the inventory belonging to the shops Phoneo has assigned to you. If you run an online store at, say, yourstore.in, this is how yourstore.in lists Phoneo stock: your server pulls the catalog, stores it on your side, and renders it.

A shop is only ever assigned to you after its owner has agreed to share their stock with you. To get access, see Getting set up.

Every endpoint is a GET. There are no write endpoints — none, anywhere.

Your key cannot create, update or delete anything inside Phoneo. It cannot mark an item sold, change a price, or place an order. This is a property of the API itself, not a permission you were given: no non-GET route exists on /api/partner/v1, and a test in Phoneo's own suite fails the build if one is ever added.

Base URLhttps://super.phoneo.in/api/partner/v1
AuthHeader X-Partner-Key
FormatJSON — send Accept: application/json
Rate limitPer key, default 120 requests/minute (429 when exceeded)
DirectionYou pull. Phoneo never calls your servers.

Authentication

Send your key on every request:

curl "https://super.phoneo.in/api/partner/v1/ping" \
  -H "X-Partner-Key: phn_live_a1b2c3d4e5f6g7h8_XXXXXXXXXXXX" \
  -H "Accept: application/json"

Keep the key on your server. Never put it in browser JavaScript, a mobile app bundle, or a public repository. Anyone holding it can read the full catalog of every shop mapped to you.

If it leaks, email support@phoneo.in immediately. Phoneo can switch the key off at once and send you a new one.

IP allowlist. If your servers have fixed outbound IPs, send them to support@phoneo.in. The key will then only work from those addresses. Strongly recommended — it turns a leaked key into a useless string.

Errors

StatuserrorWhat happened
401invalid_keyKey is wrong, or the header is missing
401key_revokedKey was revoked or has expired
403ip_not_allowedRequest came from an IP outside your allowlist
403ability_not_registeredThe endpoint exists, but Phoneo has not added it to the permission list yet
403ability_disabledThe permission exists but is currently switched off for everyone
403ability_missingThe permission is live, but your key was not given it
400invalid_key_formatThe product key in the URL is not source_type:id
404not_foundItem does not exist, or is not in your scope
422—A query parameter failed validation
429—Rate limited — honour Retry-After

The errors in the table with an error code come back as {"success": false, "error": "…", "message": "…"}. Branch on error, never on message — the message is plain English for your logs and its wording may change. Two responses have a different shape:

  • 422 — {"message": "…", "errors": {"limit": ["…"]}}, one entry per failing parameter.
  • 429 — {"message": "Too Many Attempts."} with a Retry-After header in seconds.

The three 403 permission errors are deliberately kept apart, because the fix is different for each — and none of them is something you can fix on your side. Quote the exact error string to support@phoneo.in: ability_not_registered and ability_disabled are settings on Phoneo's end, ability_missing means your key needs that permission added.

Key rotation

When Phoneo rotates your key, the new one reaches you the same way the first one did — a one-time link by email (see Getting set up). Your current key keeps working until you open that link, and for 48 hours after. Deploy the new key inside that window and your site never goes down. Build for this: read the key from config, not from a constant in code.

The three kinds of stock

Phoneo sells three different things, and they behave differently. Your catalog will contain all three mixed together, distinguished by source_type:

source_typeWhat it isNotes
used_phones A second-hand phone Every one is a unique physical handset. Carries condition, battery_health and what came in the box. Usually has a real photo gallery.
new_phone_units One unit of a brand-new phone condition is always "new". variant holds the storage size. Often only one or two catalogue images.
other_item_units One unit of an accessory or other item Chargers, cases, earbuds. condition is "other".

An item appears only when the shop has marked it visible to everyone and it is for sale. Many shops leave that switched off for new phones and accessories, so expect most of your catalog to be used_phones. Code for all three anyway — a shop can switch them on at any time.

Product key

These live in three separate tables, so a plain numeric id is not unique. Every item is identified by a composite key:

used_phones:1042
new_phone_units:88
other_item_units:310

Store this key on your side. It is what you pass to /products/{key} and to the availability check, and it never changes for the life of the item.

Everything is quantity one. Each row is one physical unit with its own history, so stock_quantity is only ever 1 or 0. Five identical chargers are five separate keys, not one row with a quantity of five. If you want to group them on your site into a single product page, group by brand + model + variant + color yourself.

Endpoints

GET /ping

Check your key and IP allowlist. Needs no particular permission — a valid key is enough, so this is the first call to make when setting up.

{
  "success": true,
  "message": "pong",
  "partner": {
    "name": "Your Store",
    "key_id": "a1b2c3d4e5f6g7h8",
    "abilities": ["catalog.*"],
    "rate_limit_per_min": 120
  },
  "shops_available": 2,
  "time": "2026-08-29T12:00:00+05:30"
}

GET /shops

The shops assigned to you. A shop is identified by its handle — that is what you pass to /products?shop=.

{
  "success": true,
  "data": [
    { "handle": "mobile-point", "name": "Mobile Point", "is_online": true }
  ]
}
This list can shrink without warning. If a shop's Phoneo subscription lapses, or the shop is deactivated, it drops out of this list and all of its products disappear from your catalog on the next sync. That is expected behaviour, not an outage.

GET /products

The catalog. This one endpoint does both the initial full import and the incremental delta sync.

ParameterTypeMeaning
shopstringLimit to one shop, by handle
source_typestringused_phones · new_phone_units · other_item_units
main_category_idintFilter by category
updated_sinceISO 8601Delta mode — also returns removed items. Any offset works (Z, +05:30, -04:00); URL-encode the + as %2B. Timestamps Phoneo returns are in IST (+05:30).
cursorintpaging.next_cursor from the previous page
limitintDefault 100, maximum 500
{
  "success": true,
  "data": [
    {
      "key": "used_phones:1042",
      "source_type": "used_phones",
      "status": "active",

      "shop": { "handle": "mobile-point", "name": "Mobile Point", "is_online": true },

      "title": "Apple iPhone 13 128GB Blue",
      "slug": "apple-iphone-13-128gb-blue",
      "brand": "Apple",
      "model": "iPhone 13",
      "variant": "128GB",
      "color": "Blue",
      "condition": "Good",
      "description": "Single owner, no scratches",

      "sale_price": "45999.00",
      "price_hidden": false,
      "currency": "INR",
      "stock_quantity": 1,

      "main_category_id": 3,
      "main_category": {
        "id": 3,
        "name": "Mobiles",
        "thumb": "https://img.phoneo.in/Phoneo/2025/Image/Enjoyer/E5/MainCat/MainCat-3/Thumb/b.jpg"
      },

      "thumb": "https://img.phoneo.in/2026/Image/E5/UsedPhone/UP-1042/Thumb/a.jpg",
      "images": ["https://img.phoneo.in/…", "https://img.phoneo.in/…"],
      "attributes": {
        "battery_health": "89%",
        "bill_box_cable_warranty": "Box only",
        "offer": "Festive"
      },

      "updated_at": "2026-08-29T11:42:10+05:30"
    }
  ],
  "paging": { "limit": 100, "next_cursor": 1042, "has_more": true }
}

Pagination

Pass paging.next_cursor back as cursor on the next request, and stop when has_more is false. The cursor is stable — new items arriving mid-walk will not make you skip or repeat a row.

Removed items — read this part twice.

When a phone sells, is hidden by the shop, or is deleted, it simply stops appearing in /products. A plain re-fetch will never tell you it is gone, so a naïve integration keeps a sold handset on sale forever.

The only way to learn about removals is the delta call:

GET /products?updated_since=2026-08-29T06:00:00%2B05:30

In that response the item comes back with "status": "removed" and "stock_quantity": 0. Take it off your site as soon as you see it.

GET /products/{key}

One item, in the same shape as a listing row. For example /products/used_phones:1042.

An item outside your scope returns 404, the same as one that does not exist — deliberately, so the endpoint cannot be used to probe for other shops' ids.

GET /products/availability

Check up to 200 items at once. Call this before you confirm any order. Your catalog copy may be minutes old, and the same handset can sell over the counter in the shop at any moment.

GET /products/availability?keys=used_phones:1042,other_item_units:310

{
  "success": true,
  "data": [
    { "key": "used_phones:1042", "status": "active", "available": true,
      "sale_price": "45999.00", "price_hidden": false,
      "updated_at": "2026-08-29T11:42:10+05:30" },

    { "key": "other_item_units:310", "status": "removed", "available": false,
      "sale_price": "1499.00", "price_hidden": false,
      "updated_at": "2026-08-29T10:10:00+05:30" }
  ]
}

keys may also be sent as an array (keys[]=…&keys[]=…). A key that is unknown or outside your scope comes back as "status": "unknown" with available: false — for your purposes the decision is the same either way: do not sell it.

GET /categories

The list of categories that currently hold a product you can see — see the Categories section.

Pricing & the hidden-price rule

sale_price is always a string, and it comes in two shapes:

  • "45999.00" — a real price
  • "X" — the shop has chosen to hide this item's price

So never feed sale_price straight into a number parser. Branch on price_hidden first:

if (product.price_hidden) {
  showEnquiryButton();            // "Price on request"
} else {
  showPrice(parseFloat(product.sale_price));
}

Price-hidden items are still real, in-stock products — list them, just without a number. Currency is always INR. This is the shop's selling price; any margin of yours is yours to add.

Categories

Each shop groups its stock into categories of its own (for example "Mobiles", "Accessories"). A product carries its category twice: as the raw id main_category_id for your own joins, and as a ready-made main_category object with the name and image filled in, so you do not need a second lookup just to render a breadcrumb.

FieldTypeMeaning
main_category.idintSame value as main_category_id
main_category.namestringDisplay name, as the shop wrote it
main_category.thumbstring · nullFull image URL, or null when the shop has not set one

Both main_category_id and main_category are null when a shop has not categorised an item — and today that is most items. Put uncategorised products in a general section rather than hiding them, or they become unreachable on your site.

GET /categories returns every category you can currently see:

{
  "success": true,
  "data": [
    {
      "id": 3,
      "name": "Mobiles",
      "thumb": "https://img.phoneo.in/Phoneo/2025/Image/Enjoyer/E5/MainCat/MainCat-3/Thumb/b.jpg",
      "sub": []
    },
    {
      "id": 7,
      "name": "Accessories",
      "thumb": null,
      "sub": []
    }
  ]
}

Only categories that actually contain a product you can see are listed, so this list shrinks and grows with the catalog. sub is always an empty array — Phoneo no longer has sub-categories (see the Changelog). The key is kept so that code written against the earlier nested shape keeps working.

How to keep your site in sync

  1. Once, at setup: walk GET /products with the cursor until has_more is false. Store every item's key and updated_at.
  2. Every 5–15 minutes: call GET /products?updated_since=<the newest updated_at you hold>. Upsert rows with status: "active"; remove rows with status: "removed".
  3. At checkout, every time: GET /products/availability for the items in the basket.
  4. Once a day: re-walk the full catalog and reconcile against your copy. This is the safety net that catches anything the delta missed.
Overlap your updated_since by a few minutes rather than using the exact last timestamp. Re-processing an item you already have costs nothing; missing one costs you a sale or leaves a sold phone on your site.
Phoneo does not reserve stock for you. The same handset is on sale at the shop counter at the same time. Design the unhappy path deliberately: check availability before taking payment, and have a clear flow for "this item just sold".

What you will never receive

These fields are deliberately excluded, and no parameter will turn them on:

  • IMEI and serial numbers (IMEI, SN, WSN)
  • Purchase price, sold price, or any margin figure
  • Supplier, vendor or customer details of any kind
  • Private/internal notes and tags
  • HSN/SAC and other tax fields
  • The shop's numeric id, phone number or address

If your integration genuinely needs something in this list, raise it with Phoneo as a scope question. It is not a setting anyone can flip on the API.

Getting set up

  1. Request access. Email support@phoneo.in with your company name, your website, the email address the key should go to, the Phoneo shops whose stock you want to list, and — if you have them — your servers' fixed outbound IPs.
  2. Shop consent. Phoneo confirms with each shop owner before assigning their shop to you. You only ever see shops that have agreed.
  3. Receive your key. Phoneo emails you a one-time link. Open it and press Reveal my API key. The key is created at that moment and shown once — copy it straight into your server's configuration. It cannot be looked up again, only replaced.
    • The link expires after 24 hours and works only once.
    • If the link says it has already been used and you did not open it, email support@phoneo.in straight away — someone else may have opened it. Phoneo will switch that key off and send you a new link.
    • If it has expired, ask for a new one. Each new link cancels the previous one.
  4. Confirm the connection with GET /ping.
  5. Do the full catalog walk, then put the 5–15 minute delta job on a schedule.

A Postman collection and environment file are available alongside this document. Questions: support@phoneo.in.

Changelog

DateWhat changedWhat you need to do
2026-09-24

Keys arrive by one-time link; clearer errors.

  • New and rotated keys are now delivered as a one-time link by email instead of being passed on by hand. On rotation, your current key keeps working until you open the link, and for 48 hours after.
  • updated_since now reads every timezone offset correctly. Before this, an offset ahead of IST (for example +08:00) was compared as if it were IST, so a delta could miss up to a few hours of changes. Z and offsets behind IST were never affected — they returned extra rows, not fewer.
  • Error message texts are now plain English. error codes, HTTP statuses and every success response are unchanged.
  • Added: 400 invalid_key_format is now listed in the error table (it already existed).
Nothing, if you branch on error. If you ever sent updated_since with an offset ahead of IST, run one full catalog walk to catch anything a delta missed.
2026-09-24

Sub-categories removed. Phoneo is retiring sub-categories across the product from 1 October 2026; shops now organise stock with one level of category only.

  • category_id and category are no longer in the product response (/products, /products/{key}).
  • The category_id filter on /products is gone. If it is still sent it is ignored, so the response is not narrowed by it.
  • /categories keeps its shape, but sub is now always [], and the "Other" bucket ("id": null) no longer appears.
  • Added: thumb on main_category and on each /categories entry.

No product values changed: category was already null on every product, so nothing that used to be filled in has been taken away. updated_at was not bumped, so this will not trigger a re-sync of your catalog.

Stop reading category_id / category; use main_category. Stop sending the category_id filter; use main_category_id.
2026-08-29 First release of v1. —

Need help?

Write to support@phoneo.in. For a permission error, include the exact error value from the response.