Skip to content
You are reading the docs for ShopClass 7.1.0.dev1, still in development. Read the stable docs.

API changelog

This page is the API’s own log. The site’s release notes link here for API changes.

The version is in the path: /api/v1. GET /api/<version>/ reports the version asked for as api_version. Each version has its own OpenAPI document at /api/<version>/openapi.json; its info.version is that document’s revision (1.0 for v1), whose minor part goes up when the version gains something.

Inside v1, a change only adds:

  • a new endpoint
  • a new member in an answer
  • a new optional parameter
  • a new value in a list of allowed values

Clients must ignore members they do not know, and must accept values they do not know in any list of allowed values: a new listing status, warnings code, field error code or webhook event can arrive inside v1. Treat an unknown error code by its status.

These need a new version, v2:

  • removing or renaming a member, endpoint or parameter
  • changing a member’s type or meaning
  • changing a status code, or the code of an existing error
  • making an optional parameter required
  • changing how authentication works

Core’s tests compare the v1 OpenAPI document with a saved copy and fail when an endpoint, member, enum value or success status is removed, a type or auth rule changes, or a request member becomes required.

If v2 ships, it runs beside v1 for at least 12 months. A core endpoint that does not change serves both; one that does keeps its v1 form under /api/v1. These stay on v1 until they opt in:

  • plugin endpoints that do not list versions
  • webhook bodies
  • osc_api_url(), unless given a version

A member is deprecated inside a version by marking it deprecated: true in the OpenAPI document and listing it below. It keeps its type and meaning until the version is retired.

  1. Announced. The endpoint or member is marked deprecated in a release and listed below with its end date.

  2. Warned. Every answer from a deprecated endpoint carries these headers:

    Header Value
    Deprecation @<unix time>: when it was deprecated (RFC 9745)
    Sunset An HTTP date: when it stops answering (RFC 8594)
    Link <…/docs/developers/api/changelog/>; rel="deprecation"

    The OpenAPI document marks the operation deprecated: true.

  3. Removed. Not before the next major release, and not sooner than six months after the Sunset header first shipped.

A route with a deprecated date but no sunset has no removal date yet.

Plugin endpoints can use the same headers: set deprecated and sunset in the route spec (Plugin endpoints).

To catch a deprecation early, log any answer that has a Deprecation header.

None.

Added to v1 on 2026-10-23.

  • GET and POST /account/conversations, GET /account/conversations/{id}, GET and POST /account/conversations/{id}/messages, and POST /account/conversations/{id}/read, /block (DELETE lifts it) and /report. Scopes conversations:read and conversations:write.
  • GET /admin/conversations, GET /admin/conversations/{id}, POST /admin/conversations/{id}/close and /clear-report, GET /admin/messages, GET /admin/messages/{id}, POST /admin/messages/{id}/release and /remove. Scope admin:conversations, also for moderator keys.
  • New warning message_held.
  • New webhook events message.created and conversation.reported; they carry ids only.

Added to v1 on 2026-10-22.

  • With count=true, a listing search’s meta has total_capped. When true, counting stopped at the site’s search count limit: total is that limit and more listings match.

Added to v1 on 2026-10-19.

  • A listing can be unpaid, in Listing.status and in the status filter of GET /account/listings and GET /admin/listings. POST /listings may answer 201 with that status, the listing_unpaid warning and a payment member.
  • POST /listings/{id}/publish publishes it with credits, or with {package, gateway} starts an order and answers order and next. POST /admin/listings/{id}/publish publishes without payment.
  • New error insufficient_credits (422, with payment) and new warning listing_unpaid.
  • listing.created is sent when a listing that waited for payment goes live.
  • The site document’s mode is classifieds or shop; both is gone.

Added to v1.

  • GET /cart/quote?lines=variant:qty,…: a cart priced on the server with delivery, fees, totals and the payment methods on offer. Needs no sign-in.
  • POST /account/orders, GET /account/orders, GET /account/orders/{id}, POST /account/orders/{id}/pay and POST /account/orders/{id}/cancel, with the scopes orders:read and orders:write.
  • GET /account/cart and PUT /account/cart: the signed-in shopper’s saved cart, with the scopes cart:read and cart:write.
  • New field error codes out_of_stock, not_buyable, unavailable and too_many_open.

Added to v1.

  • GET /pages, GET /pages/{id|name} and GET /menus: linked pages, one page with its text, and the site menus (main, footer). A plugin adds or edits menus with the api_menus filter. All take locale.
  • GET /seo?for=home|listing|category|page|search: canonical URL, title, description, robots, breadcrumbs, hreflang and Open Graph. A hidden or unknown target is a 404.
  • The site document carries mode and features.shop. In shop mode POST /listings answers 403 feature_disabled.
  • A listing has a product member with its variants (SKU, price, stock, weight, shipping class) when it is for sale. Only an admin writes them.
  • product.options lists the option names and values (Colour, Size); variants[].photo is the variant’s photo. An admin sends option rows as product.variants.
  • Reviews: a comment carries rating (1 to 5 or null), and POST /listings/{id}/comments takes rating (a comment may have text, a rating or both; a second rated comment is 409 already_reviewed; while features.ratings is false a rating is 403 feature_disabled). GET /listings/{id}/comments takes sort=newest. include=rating adds rating: {average, count} to a listing, with distribution on one listing.

Added to v1.

  • GET /listings/{id}/related: live listings like one listing, best first. limit 1-20 (6); same_category (on by default), same_country, same_region, with_photos, premium_only.
  • GET /search/suggest?q=: listings for a search box, as {id, title, url, thumbnail, price, place}, plus any searches a search plugin adds, each with a translated count_label. Titles only, all words first, then titles with some of them; q needs a word of three letters or more. limit 1-10 (5).
  • GET /search/categories?title=: up to three categories for a new listing’s title, from listings whose titles share the most words with it.
  • On a keyword search each listing has snippet: its text with the searched words in <mark>.
  • A listing search’s meta may carry facets.category, suggestion and relaxed when a search plugin sent them.

Added to v1.

  • GET /listings and GET /users/{id}/listings take lat, lng and radius (km), and sort=distance for nearest first. With no radius, nearest first keeps to 1000 km.
  • They answer 422 when distance search is off on the site, and when radius or sort=distance comes without both lat and lng.
  • Each listing’s location has distance_km: whole km from lat/lng, even on a site that shows miles, or null outside a distance search.

Added to v1.

Endpoint Does
/admin/listings, with bump Moderate and edit any listing
/admin/comments, with approved and blocked on PATCH Moderate comments
/admin/users, with confirmed and blocked on PATCH, and /admin/users/{id}/sessions Manage users and end their sign-ins
GET /account/listings The user’s own listings in any status
/admin/categories, /admin/currencies, /admin/custom-fields Manage categories, currencies and custom fields
/admin/regions, /admin/cities, /admin/areas Manage locations
GET, PATCH /admin/settings, GET /admin/jobs Settings from a fixed list, and the job queue
/admin/keys, with rotate Manage API keys
/admin/webhooks, with rotate-secret, test, deliveries, and GET /admin/webhook-events Manage webhook endpoints
GET /comments/{id}, GET /listings/{id}/photos/{photo}, GET /account/keys/{id}, GET /account/alerts/{id} Read one thing back, so every Location can be read

Also added:

  • Webhooks: signed deliveries in the Standard Webhooks layout for eleven events (listing.created, listing.updated, listing.deleted, listing.activated, listing.deactivated, listing.spam, listing.reported, comment.created, user.registered, user.updated, user.deleted), with retries, auto-pause and secret rotation. Plugins add events named ext.<slug>.<name>.
  • Scopes: admin:comments, admin:settings, admin:keys, admin:webhooks. admin:listings, admin:users and admin:taxonomy also open their admin endpoints.
  • An api member on GET /: registration, personal_keys, photo_urls.
  • Hooks: api_webhook_events, api_webhook_payload, api_webhook_delivered. Function: osc_webhook_emit().
  • For plugins: ApiKit::listingContext(), Scopes::PLUGIN_SCOPE and the Scopes::AUDIENCE_* constants, RouteSpec::auth(), scope() and plugin(), and osc_user_api_access_url().
  • The api_listing, api_user and api_category filters get $data, $context; the database row is no longer passed.
  • Settings: Allow webhook addresses on a private network.
  • No new error codes. Admin actions the state refuses answer 409 conflict.

See Admin endpoints and Webhooks.

Added to v1.

Endpoint Does
POST /auth/token Sign in with a password, or swap a refresh token for new tokens
POST /auth/sign-out Sign out this sign-in
GET, PATCH /account, POST /account/password The user’s own profile and password
GET /account/sessions, DELETE /account/sessions/{id} The user’s sign-ins
GET, POST /account/keys, DELETE /account/keys/{id} Personal API keys, when the site allows them
POST /users Sign up, when the site allows it
POST /listings, PATCH /listings/{id}, DELETE /listings/{id} Post, edit and delete the user’s listings
POST /photos, POST /listings/{id}/photos, DELETE /listings/{id}/photos/{photo} Upload, add and remove photos
POST /listings/{id}/comments, DELETE /comments/{id} Comment, and delete one’s own comment
GET, POST /account/alerts, DELETE /account/alerts/{id} Saved searches

Also added:

  • Credentials: access tokens (sca_, 15 minutes), single-use refresh tokens (scr_) with reuse detection, and personal keys (sck_) that expire within a year and stop when the password changes.
  • Scopes held by users: listings:write, listings:delete, comments:write, alerts:read, alerts:write, account:read, account:write.
  • The owner view for a user’s own listings and account.
  • Idempotency-Key on writes, answered again with Idempotency-Replayed: true.
  • PATCH takes application/merge-patch+json and answers Accept-Patch.
  • Location on every 201, and warnings next to data: listing_pending, photo_skipped, comment_pending, email_confirmation_sent.
  • Error codes: token_expired, not_owner, listing_limit, login_blocked, idempotency_key_reused, idempotency_in_flight, conflict, invalid_header, and at POST /auth/token the OAuth 2 ones: invalid_request, invalid_grant, invalid_scope, unsupported_grant_type. The token endpoint also takes a form.
  • Settings: personal keys, sign-up, photos by address, and new listings per user an hour.
  • Hourly caps for new listings, comments, photo downloads, e-mail changes and sign-ups.

See Writes and Authentication.

Read-only endpoints, authenticated with keys.

Endpoint Returns
GET / The site, its locales and links
GET /openapi.json The OpenAPI 3.1 document, with the site’s plugin endpoints. No credential needed.
GET /listings, GET /listings/{id} Search, and one listing
GET /listings/{id}/photos, GET /listings/{id}/comments A listing’s photos and approved comments
GET /categories, GET /categories/{category}, GET /custom-fields Categories (flat or tree) and custom fields
GET /countries, /countries/{code}/regions, /regions/{id}/cities, /cities/{id}/areas Locations
GET /currencies Currencies
GET /users/{id}, GET /users/{id}/listings A public profile and the user’s live listings

Also in the first release:

  • Public keys (scp_) and admin keys (sck_), made on Admin → Settings → API or with api:key:create.
  • Cursor paging, sparse fieldsets (fields), ETag and If-None-Match.
  • Rate limits with RateLimit and X-RateLimit-* headers, and a lockout after failed keys.
  • CORS for origins the site owner lists.
  • Errors as application/problem+json.
  • For plugins: ext/<slug>/ routes, ext:<slug>:<verb> scopes, and declared fields under ext.