Download the PHP package christianjbrown/etsy-open-api-sdk without Composer
On this page you can find all versions of the php package christianjbrown/etsy-open-api-sdk. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package etsy-open-api-sdk
Etsy Open API v3 SDK
A strongly-typed PHP client for the Etsy Open API v3. It reads and writes your shop's data — receipts, listings, shipping profiles, and more — returning plain, typed model objects rather than raw arrays, and taking plain, typed request objects for anything you create or update.
Every operation in Etsy's published OpenAPI spec is covered: all GET endpoints, and every POST, PUT, PATCH and DELETE write. A write beyond the shop the client is configured for needs the matching OAuth scope granted to your access token (see "Prerequisites" below) — the read side currently supports:
- Reading shop receipts — a page of the shop's receipts (
getMultiple), a page plus the shop's total receipt count so you can walk the whole set (getPage), or a single receipt by id (getOneById). Each receipt carries the full order: buyer and address fields, the money totals (grandtotal, subtotal, shipping, tax, VAT, discount, gift wrap), and its nested transactions, refunds, and shipments.
Supported read endpoints
| Resource | Client | Endpoint(s) | Returns |
|---|---|---|---|
| Shop receipts | getShopReceiptApi() |
GET /shops/{shop_id}/receipts, GET /shops/{shop_id}/receipts/{receipt_id} |
ReceiptInterface[] / ReceiptPageInterface / ReceiptInterface |
| Shop receipt transactions | getShopReceiptTransactionApi() |
GET /shops/{shop_id}/transactions/{transaction_id}, GET /shops/{shop_id}/receipts/{receipt_id}/transactions, GET /shops/{shop_id}/listings/{listing_id}/transactions, GET /shops/{shop_id}/transactions |
TransactionInterface / TransactionInterface[] |
| Payments | getPaymentApi() |
GET /shops/{shop_id}/payments, GET /shops/{shop_id}/receipts/{receipt_id}/payments, GET /shops/{shop_id}/payment-account/ledger-entries/payments |
PaymentInterface[] |
| Ledger entries | getLedgerEntryApi() |
GET /shops/{shop_id}/payment-account/ledger-entries, GET /shops/{shop_id}/payment-account/ledger-entries/{ledger_entry_id} |
PaymentAccountLedgerEntryInterface[] / PaymentAccountLedgerEntryInterface |
| Listings | getShopListingApi() |
GET /listings/{listing_id}, GET /shops/{shop_id}/listings, GET /listings/active, GET /shops/{shop_id}/listings/active, GET /listings/batch, GET /shops/{shop_id}/listings/featured, GET /shops/{shop_id}/policies/return/{return_policy_id}/listings, GET /shops/{shop_id}/receipts/{receipt_id}/listings, GET /shops/{shop_id}/shop-sections/listings |
ListingInterface / ListingInterface[] |
| Batch listings with associations | getListingBatchApi() |
GET /listings/batch/inventory, GET /listings/batch/shipping |
ListingWithAssociationsInterface[] |
| Listing files | getListingFileApi() |
GET /shops/{shop_id}/listings/{listing_id}/files, GET /shops/{shop_id}/listings/{listing_id}/files/{listing_file_id} |
ListingFileInterface[] / ListingFileInterface |
| Listing images | getListingImageApi() |
GET /listings/{listing_id}/images, GET /listings/{listing_id}/images/{listing_image_id} |
ListingImageInterface[] / ListingImageInterface |
| Listing videos | getListingVideoApi() |
GET /listings/{listing_id}/videos, GET /listings/{listing_id}/videos/{video_id} |
ListingVideoInterface[] / ListingVideoInterface |
| Listing variation images | getListingVariationImageApi() |
GET /shops/{shop_id}/listings/{listing_id}/variation-images |
ListingVariationImageInterface[] |
| Listing properties | getListingPropertyApi() |
GET /shops/{shop_id}/listings/{listing_id}/properties, GET /listings/{listing_id}/properties/{property_id} |
ListingPropertyValueInterface[] / ListingPropertyValueInterface |
| Listing inventory | getListingInventoryApi() |
GET /listings/{listing_id}/inventory, GET /listings/{listing_id}/inventory/products/{product_id}, GET /listings/{listing_id}/products/{product_id}/offerings/{product_offering_id} |
ListingInventoryInterface / ListingInventoryProductInterface / ListingInventoryProductOfferingInterface |
| Listing translations | getListingTranslationApi() |
GET /shops/{shop_id}/listings/{listing_id}/translations/{language} |
ListingTranslationInterface |
| Listing personalization | getListingPersonalizationApi() |
GET /listings/{listing_id}/personalization |
ListingPersonalizationInterface |
| Shops | getShopApi() |
GET /shops/{shop_id}, GET /users/{user_id}/shops, GET /shops?shop_name=… |
ShopInterface / ShopInterface[] |
| Shop sections | getShopSectionApi() |
GET /shops/{shop_id}/sections, GET /shops/{shop_id}/sections/{shop_section_id} |
ShopSectionInterface[] / ShopSectionInterface |
| Seller taxonomy | getSellerTaxonomyApi() |
GET /seller-taxonomy/nodes, GET /seller-taxonomy/nodes/{taxonomy_id}/properties |
SellerTaxonomyNodeInterface[] / TaxonomyNodePropertyInterface[] |
| Buyer taxonomy | getBuyerTaxonomyApi() |
GET /buyer-taxonomy/nodes, GET /buyer-taxonomy/nodes/{taxonomy_id}/properties |
BuyerTaxonomyNodeInterface[] / BuyerTaxonomyNodePropertyInterface[] |
| Reviews | getReviewApi() |
GET /shops/{shop_id}/reviews, GET /listings/{listing_id}/reviews |
ReviewInterface[] |
| Shop return policies | getShopReturnPolicyApi() |
GET /shops/{shop_id}/policies/return, GET /shops/{shop_id}/policies/return/{return_policy_id} |
ShopReturnPolicyInterface[] / ShopReturnPolicyInterface |
| Shop production partners | getShopProductionPartnerApi() |
GET /shops/{shop_id}/production-partners |
ShopProductionPartnerInterface[] |
| Shop holiday preferences | getShopHolidayPreferenceApi() |
GET /shops/{shop_id}/holiday-preferences |
ShopHolidayPreferenceInterface[] |
| Shop readiness state definitions | getShopReadinessStateDefinitionApi() |
GET /shops/{shop_id}/readiness-state-definitions, GET /shops/{shop_id}/readiness-state-definitions/{readiness_state_definition_id} |
ShopReadinessStateDefinitionInterface[] / ShopReadinessStateDefinitionInterface |
| Shipping profiles | getShippingProfileApi() |
GET /shops/{shop_id}/shipping-profiles, GET /shops/{shop_id}/shipping-profiles/{shipping_profile_id}, GET /shops/{shop_id}/shipping-profiles/{shipping_profile_id}/destinations, GET /shops/{shop_id}/shipping-profiles/{shipping_profile_id}/upgrades, GET /shipping-carriers?origin_country_iso=… |
ShopShippingProfileInterface[] / ShopShippingProfileInterface / ShopShippingProfileDestinationInterface[] / ShopShippingProfileUpgradeInterface[] / ShippingCarrierInterface[] |
| Users | getUserApi() |
GET /users/{user_id}, GET /users/me |
UserInterface |
| User addresses | getUserAddressApi() |
GET /user/addresses, GET /user/addresses/{user_address_id} |
UserAddressInterface[] / UserAddressInterface |
| Ping | getPingApi() |
GET /openapi-ping |
PingInterface |
Supported write endpoints
Every write takes a Model\XRequest object (constructed directly, or via its fluent setters for the optional fields) and returns the same typed model a read would. DELETE operations return void. See "Prerequisites" below for how OAuth scopes work.
| Verb | Endpoint | Required scope | Client · Method |
|---|---|---|---|
POST |
/shops/{shop_id}/listings |
listings_w |
getShopListingApi()->create() |
PATCH |
/shops/{shop_id}/listings/{listing_id} |
listings_w |
getShopListingApi()->update() |
DELETE |
/listings/{listing_id} |
listings_d |
getShopListingApi()->delete() |
PUT |
/shops/{shop_id}/listings/{listing_id}/properties/{property_id} |
listings_w |
getListingPropertyApi()->update() |
DELETE |
/shops/{shop_id}/listings/{listing_id}/properties/{property_id} |
listings_w |
getListingPropertyApi()->delete() |
POST |
/shops/{shop_id}/listings/{listing_id}/files |
listings_w |
getListingFileApi()->upload() |
DELETE |
/shops/{shop_id}/listings/{listing_id}/files/{listing_file_id} |
listings_w |
getListingFileApi()->delete() |
POST |
/shops/{shop_id}/listings/{listing_id}/images |
listings_w |
getListingImageApi()->upload() |
DELETE |
/shops/{shop_id}/listings/{listing_id}/images/{listing_image_id} |
listings_w |
getListingImageApi()->delete() |
POST |
/shops/{shop_id}/listings/{listing_id}/videos |
listings_w |
getListingVideoApi()->upload() |
DELETE |
/shops/{shop_id}/listings/{listing_id}/videos/{video_id} |
listings_w |
getListingVideoApi()->delete() |
PUT |
/listings/{listing_id}/inventory |
listings_w |
getListingInventoryApi()->update() |
POST |
/shops/{shop_id}/listings/{listing_id}/personalization |
listings_w |
getListingPersonalizationApi()->update() |
DELETE |
/shops/{shop_id}/listings/{listing_id}/personalization |
listings_w |
getListingPersonalizationApi()->delete() |
POST |
/shops/{shop_id}/listings/{listing_id}/translations/{language} |
listings_w |
getListingTranslationApi()->create() |
PUT |
/shops/{shop_id}/listings/{listing_id}/translations/{language} |
listings_w |
getListingTranslationApi()->update() |
POST |
/shops/{shop_id}/listings/{listing_id}/variation-images |
listings_w |
getListingVariationImageApi()->update() |
PUT |
/shops/{shop_id}/receipts/{receipt_id} |
transactions_w |
getShopReceiptApi()->updateShopReceipt() |
POST |
/shops/{shop_id}/receipts/{receipt_id}/tracking |
transactions_w |
getShopReceiptApi()->createReceiptShipment() |
PUT |
/shops/{shop_id} |
shops_r + shops_w |
getShopApi()->updateShop() |
PUT |
/shops/{shop_id}/holiday-preferences/{holiday_id} |
shops_w |
getShopHolidayPreferenceApi()->updateHolidayPreference() |
POST |
/shops/{shop_id}/sections |
shops_w |
getShopSectionApi()->create() |
PUT |
/shops/{shop_id}/sections/{shop_section_id} |
shops_w |
getShopSectionApi()->update() |
DELETE |
/shops/{shop_id}/sections/{shop_section_id} |
shops_w |
getShopSectionApi()->delete() |
POST |
/shops/{shop_id}/policies/return |
shops_w |
getShopReturnPolicyApi()->create() |
PUT |
/shops/{shop_id}/policies/return/{return_policy_id} |
shops_w |
getShopReturnPolicyApi()->update() |
DELETE |
/shops/{shop_id}/policies/return/{return_policy_id} |
shops_w |
getShopReturnPolicyApi()->delete() |
POST |
/shops/{shop_id}/policies/return/consolidate |
shops_w |
getShopReturnPolicyApi()->consolidate() |
POST |
/shops/{shop_id}/readiness-state-definitions |
shops_w |
getShopReadinessStateDefinitionApi()->create() |
PUT |
/shops/{shop_id}/readiness-state-definitions/{readiness_state_definition_id} |
shops_w |
getShopReadinessStateDefinitionApi()->update() |
DELETE |
/shops/{shop_id}/readiness-state-definitions/{readiness_state_definition_id} |
shops_w |
getShopReadinessStateDefinitionApi()->delete() |
POST |
/shops/{shop_id}/shipping-profiles |
shops_w |
getShippingProfileApi()->create() |
PUT |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id} |
shops_w |
getShippingProfileApi()->update() |
DELETE |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id} |
shops_w |
getShippingProfileApi()->delete() |
POST |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id}/destinations |
shops_w |
getShippingProfileApi()->createDestination() |
PUT |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id}/destinations/{shipping_profile_destination_id} |
shops_w |
getShippingProfileApi()->updateDestination() |
DELETE |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id}/destinations/{shipping_profile_destination_id} |
shops_w |
getShippingProfileApi()->deleteDestination() |
POST |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id}/upgrades |
shops_w |
getShippingProfileApi()->createUpgrade() |
PUT |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id}/upgrades/{upgrade_id} |
shops_w |
getShippingProfileApi()->updateUpgrade() |
DELETE |
/shops/{shop_id}/shipping-profiles/{shipping_profile_id}/upgrades/{upgrade_id} |
shops_w |
getShippingProfileApi()->deleteUpgrade() |
DELETE |
/user/addresses/{user_address_id} |
address_r |
getUserAddressApi()->delete() |
POST |
/scopes |
none | getPingApi()->getScopes() |
:heavy_check_mark: Prerequisites
- Git
- PHP 8.5 or higher (8.x)
- Composer
- An Etsy OAuth access token carrying the scope(s) your calls need. Every read endpoint needs
the matching
_rscope (listings_r,transactions_r,shops_r, …); every write additionally needs the matching_wscope (listings_w,listings_dfordeleteListing,transactions_w,shops_w), anddeleteUserAddressneedsaddress_r. See the "Supported write endpoints" table above for the exact scope each write call needs, and Etsy's own scopes documentation for how scopes are requested during the OAuth authorization flow. A token missing a scope gets a403from Etsy, not a client-side error — this library does not validate scopes locally.
:bulb: If you're on MacOS and have Homebrew, PHP and Composer will install with brew install composer.
:building_construction: Installation
For your composer-enabled project:
:computer: Usage
Etsy's Open API v3 authenticates every request with two pieces: your app's keystring (sent as the x-api-key header) and an OAuth 2.0 access token (sent as Authorization: Bearer …). Access tokens are short-lived, so this client refreshes them for you using a long-lived refresh token and the OAuth2 refresh_token grant.
You supply four things to EtsyFactory::create():
- your numeric shop id,
- your app keystring (which Etsy also uses as the OAuth
client_id), - a
TtlAwareKeyValueStoreInterfaceto hold the current access token (an in-memory store is fine — it's re-fetched as needed), - a
KeyValueStoreInterfaceholding your refresh token. This one must persist (a database, secret store, etc.), because Etsy rotates the refresh token on every refresh and the client writes the new value back. Seed it once with a refresh token obtained from Etsy's OAuth authorization flow.
Reading receipts then looks like this:
getMultiple() hands back just the receipts on the page, which is enough when you
only want the most recent orders. To walk every receipt, use getPage() instead:
it returns a ReceiptPageInterface carrying the shop's total receipt count
alongside the page, so the loop knows when to stop without having to guess from a
short page.
Writing data follows the same shape: build a Model\XRequest, and call the matching client method. Optional fields are set through fluent setters, exactly like a model's getters mirror the response:
See "Supported write endpoints" above for the full list of write calls and their required OAuth scopes.
Pointing at a different host
EtsyFactory::create() uses Etsy's production hosts. EtsyFactory::createForHost() takes the same
arguments plus an EtsyHostInterface. Pass a differently configured EtsyHost to point every request, and the
OAuth token refresh, somewhere else — a test double, a proxy, or a sandbox once Etsy publishes one:
Etsy's Open API v3 has no published sandbox at the time of writing (unlike, say, eBay's
api.sandbox.ebay.com/apiz.sandbox.ebay.com), so the only two hosts this library knows about are
the production ones above. EtsyHost exists so a test double or a future sandbox host can be
swapped in without editing any Api/ class.
Upgrading to 2.0
Etsy no longer builds its own services, so it is no longer constructed with the shop id, keys and
stores. Build it with EtsyFactory instead:
new Etsy($container) now takes a PSR-11 container and is meant for code that wires its own graph.
If you construct ListingWithAssociationsTransformer yourself, it now takes an array of
ListingWithAssociationsFieldsTransformerInterface implementations.
This release also moves to christianjbrown/api-client 3, christianjbrown/oauth2-client 2.1 and
christianjbrown/key-value-store 3. The key-value stores now need a PSR-20 clock, so build the
in-memory stores as new MemoryKeyValueStore(new NativeClock()) (symfony/clock) and give
FirestoreKeyValueStore a clock as its second argument. In-memory stores now enforce TTLs.
If you wire clients by hand, ApiClient is built with (new ApiClientFactory(new ClientOptions()))->create()
and RefreshTokenManager with (new RefreshTokenManagerFactory(new NativeClock()))->create(...), which
takes a client authentication (PublicClientAuthentication for Etsy) and a lock (NullLock for none).
:rotating_light: Error handling
Everything this library throws implements ChristianBrown\Etsy\Exception\ExceptionInterface, so a single catch covers it all:
There are two concrete types:
UnexpectedResponseException(extendsRuntimeException) — the Etsy API returned a body the client or a transformer couldn't parse (a missing/mis-typed field, an empty response).MissingInputException(extendsInvalidArgumentException) — bad caller input.
Both live in src/Exception/. Request-level failures (network errors, non-2xx responses) surface as RequestExceptionInterface from christianjbrown/api-client; token-refresh failures surface as RequestExceptionInterface from christianjbrown/oauth2-client. Both are outside this library's exception hierarchy.
Under the hood, Etsy wires the clients, their transformer chains, and the OAuth refresh machinery through a Symfony dependency-injection container. If you don't want the container, you can build the same chain by hand — as shown below.
Wiring the clients
Every `Api/` class keeps its own in-memory response cache behind `ChristianBrown\Etsy\Cache\ResponseCacheInterface`, one instance per cache the class needs — `ShopReceiptApi` above has three, one each for `getMultiple()`, `getPage()` and `getOneById()`. `ResponseCache` is the plain implementation used by the container; a hand-wired client can use it too, or supply its own.:memo: Changelog
Notable changes in each release are listed in CHANGELOG.md.
:page_facing_up: License
Released under the MIT License.
All versions of etsy-open-api-sdk with dependencies
christianjbrown/api-client Version ^3.0
christianjbrown/key-value-store Version ^3.0
christianjbrown/oauth2-client Version ^2.1
psr/container Version ^2.0
symfony/dependency-injection Version ^8.0
symfony/clock Version ^8.0