Python client
This client is for a Python backend (Django, FastAPI, a background worker) that would rather
not hand-roll HTTP calls. The meisei package covers the admin API and the widget API, and is
generated from the same OpenAPI documents this reference is built from, so it never drifts
from what the API accepts. It is built on httpx and pydantic, is fully typed, and needs
Python 3.10 or later.
Every Python example in this reference uses it.
Install

The admin API
Build a client with a secret key and your store's base URL (see Your base URL). Keep it on your server. A secret key that reaches a browser has leaked. One client is safe to share between threads, so an app can build it once at startup.
Leave both arguments out and the client reads MEISEI_SECRET_KEY and MEISEI_BASE_URL
from the environment. An app installed through OAuth passes
access_token= instead of secret_key=.
Calling an endpoint
Endpoints are grouped the way their paths are. The usual five are list, retrieve,
create, update and delete, and anything else is named for what it does:
import_ has a trailing underscore because import is a Python keyword. The reference shows
the Python call for every endpoint. Path parameters are positional, in the order the path
names them. Everything else, query parameters and body fields alike, is a keyword argument in
snake_case, and so are the keys of the dicts you pass inside a body. The client sends each one
where it belongs, under the API's own name.
Passing None sends null, which clears a field that can be cleared. Leaving an argument out
leaves the field as it is. Every argument is typed, so your editor and a type checker such as
mypy catch a misspelled or missing one before anything is sent, and Python raises a
TypeError if one gets through.
A few values are converted for you:
- A list for a comma-separated parameter:
include=["orders_count", "tags"]. - A dict for
filter, which is sent as its JSON document. See Filtering. - A sort order or an
includevalue in snake_case:sort="-created_at". - A
datetimeor adate, which is sent as ISO 8601.
The keys of a dict you send are left alone wherever they are yours, such as metadata, so
metadata={"giftWrap": True} arrives exactly as written.
Reading a response
A record comes back as a pydantic model with an attribute for each field, in snake_case.
The records this reference names (a review, a product, a customer) have their own classes in
meisei.types, such as Review, and nested records are models too. Timestamps
are datetimes.
A field added to the API after your version of the package still reads the same way, so you
only need to upgrade for new endpoints. Records also work with match:
Lists and pagination
A list comes back as a page. page.data is the records on it, and looping over the page
keeps fetching pages with the same parameters until the last one, so a whole collection is
one loop:
The loop fetches lazily, so itertools.islice(meisei.customers.list(), 10) stops once it has
ten. See Pagination for how cursors behave.
Errors
A failed request raises. Every error the package raises is a MeiseiError, and every answer
from the API that is an error is an APIError carrying status, code and message (see
Errors for the codes):
Retries and timeouts
The client retries a rate-limited request after the Retry-After interval the API gives it.
It also retries a 5xx or a dropped connection, with a short backoff, but only on GET,
PUT and DELETE, so a POST is never sent twice. Two retries is the default. Each request
is logged at debug level and each retry as a warning, on the meisei logger unless
you pass your own.
with_options gives a copy for one call, sharing the same connections. Every method also
takes timeout= and extra_headers= for itself:
Async
AsyncClient takes the same arguments, every method is awaited, and a list pages with
async for:
The widget API
WidgetClient calls the widget API. From a server, give it a secret key. It reads what the
publishable key reads, and draws on your organization's API allowance
rather than one shopper's. See
Calling the widget API from a server.
A publishable_key= works in place of the secret key, for code that runs where a secret key
must not. AsyncWidgetClient is the async version.
Identifying the shopper
Anything personal, such as a shopper's wishlists or loyalty balance, needs their signed email.
Pass customer= and the client does the rest. Give the client your widget signing secret and
customer= takes the email itself:
To keep the signing secret out of your own code, ask the admin API to sign instead, and pass
what it returns. The key needs the customers:write permission.
sign_customer signs an email locally, which is also how to hand a signature to a page so
the storefront widgets can identify the shopper. See
Identifying the customer.
Talking to several organizations
A client holds one key and one base URL, so build one per organization, passing the same
http_client= to share a connection pool between them. If you are building an application
many merchants install, use OAuth rather than asking each of them for a secret key, and pass
each one's token as access_token=. See Authentication.