shoply is the API for a store’s own server in Python: every route of this reference is a method, with types your editor and mypy read, retries that are safe to repeat, lists a page at a time or all at once, and a check that a webhook came from Shoply. Each route’s page shows its call.
SHOPLY_API_KEY. A key is a secret: keep it on your server, never in a page. For a buyer’s own browser, see the Customer API.
A route is a method
A route’s method is its name in this reference, and its page shows the call. Get an order isorders.show, so client.orders.show(order_id). A call takes:
- The IDs in the route’s address, in order. The store’s own is filled in for you.
- Its fields, as keyword arguments named as the reference names them: what goes in the address and what goes in the body are sorted out for you. A name that is a Python keyword takes an underscore:
from_, and Import a storefront isstorefronts.import_. - Options, as keyword arguments too:
idempotency_key,timeout,max_retries,headers.
data, as dicts and lists. A field left as None is not sent. The store is asked of the key once, the first time it is needed (GET /key). Pass store_id to skip that.
Lists
A list that is paged answers ashoply.Page, and a for loop over it walks every row of every page, asking for the next as it goes.
data, its rows, page, pages, total, and meta, whatever else the list answers beside its rows (counts, figures). A list that is not paged, such as List products, answers its rows.
Errors
A refusal is ashoply.ShoplyError.
Trying again
Every change is sent with an Idempotency-Key, a new one for each call and the same on each of its tries. So a try whose answer was lost is answered as the first one was, never carried out twice. To make a change once however many times your own code runs it, pass your own:request_in_progress. It waits as long as Retry-After says, or half a second, then a second, then two, a little more or less. Told to wait more than a minute, it does not, and raises with retry_after. An upload is not tried again, since it carries no key.
Pass max_retries=0 to a call, or to the client, to try once.
Files
A route that answers a file, a CSV or a PDF, answers ashoply.File: its body as bytes, as it came, content_type, text(), and save(path).
file as a path, the file’s bytes, or a file opened for reading, with filename and content_type where you want to say them. What else the route takes goes beside it:
A route newer than the package
request() reaches any route by its method and its address after /v1, with {storeId} filled in. It answers the whole envelope (Conventions).
Webhooks
shoply.webhooks.verify checks a delivery’s signature with your endpoint’s signing secret, refuses one signed more than five minutes ago, and answers the event. Pass the raw body, as it arrived, and the request’s headers, or a WSGI environ. See Webhooks.
request.body and the headers request.headers. shoply.webhooks.sign(body, secret) makes the headers Shoply would send, to test your handler with.
The Customer API
shoply.Customer is the Customer API: a buyer signs in with a code emailed to them, and reads their own orders, keys, subscriptions and affiliate page at your store. It holds the buyer’s token and no key.
buyer.token and pass it back as token next time. A change made with a buyer’s token is never sent again by itself.
OAuth
An app that sellers connect to their own stores (see Apps and OAuth) passes the token it is handed as its key, andshoply.oauth has the steps that get one: authorize_url(), state(), pkce(), exchange() and revoke().
ShoplyError whose code is OAuth’s word: invalid_grant, invalid_client. A notice that a seller disconnected your app is checked with shoply.webhooks.verify and your app’s notice secret.
Types
Each method says what it takes, with each field’s words, and what it answers, as aTypedDict. VS Code, PyCharm and mypy read them, so a missing field, a value a field does not take or a misspelt key is caught as you write it.
What a route answers is described from an answer that route gave, as its page’s example is. A field that was text there may be None in another answer, a whole number may have a fraction in another and is typed float, and a route with no example answers Any.
