Unofficial Loops.so Python Library (pyloops-so)
pyloops-so is a, unofficial lightweight Python SDK for the Loops API, designed for production usage with minimal dependencies.
Why this library
- Complete support for Loops.so endpoints
- Typed request/response models via Pydantic
- Optional raw JSON mode when you want plain dictionaries
- Lightweight runtime dependencies (
pydantic,requests) - Small, composable client structure (
contacts,events,transactional, etc.)
Loops API docs
Official Loops API reference: https://loops.so/docs/api-reference
Endpoint docs covered by this SDK:
- Create Contact
- Update Contact
- Find Contact
- Delete Contact
- Create Contact Property
- List Contact Properties
- List Mailing Lists
- Send Event
- Send Transactional Email
- List Transactional Emails
- API Key
- Dedicated Sending IPs
Installation
Install from PyPI:
uv add pyloops-so
For local development:
uv sync --extra dev
Examples
- Cognito custom email sender Lambda example: examples/cognito_email_sender.py
Authentication
Loops uses Bearer auth for all endpoints:
Authorization: Bearer {api_key}
Create a client once and reuse it:
from loops_py import LoopsClient
client = LoopsClient(api_key="loops_api_key")
If your network/provider applies strict bot filtering, set an explicit user agent:
client = LoopsClient(api_key="loops_api_key", user_agent="my-app/1.0")
Usage model
The SDK supports two call styles:
- Top-level convenience methods (
client.create_contact(...)) for compatibility. - Grouped service methods (
client.contacts.create_contact(...)) for clearer organization.
Both call styles use the same underlying implementation.
Typed mode (default)
By default, responses are returned as Pydantic models.
from loops_py import ContactRequest, LoopsClient
client = LoopsClient(api_key="loops_api_key")
created = client.contacts.create_contact(
ContactRequest(
email="ada@example.com",
first_name="Ada",
user_id="usr_123",
mailing_lists={"cll2pyfrx0000mm080fwnwdg0": True},
)
)
print(created.success)
print(created.id)
JSON mode
If you prefer raw dict/list responses, use response_mode="json".
from loops_py import LoopsClient
client = LoopsClient(api_key="loops_api_key", response_mode="json")
raw = client.account.verify_api_key()
print(raw["teamName"])
You can override per call:
typed = client.account.verify_api_key(as_json=False)
raw = client.account.verify_api_key(as_json=True)
Error handling
HTTP errors from Loops raise LoopsAPIError with status code and parsed response payload.
from loops_py import LoopsAPIError
try:
client.contacts.find_contact({"email": "missing@example.com"})
except LoopsAPIError as exc:
print(exc.status_code)
print(exc.response)
Rate limit handling and retries
Loops applies request rate limits (baseline 10 requests/second/team) and can return 429.
This SDK retries 429 responses automatically with exponential backoff.
Default retry behavior:
max_retries=3(up to 4 total attempts)retry_backoff_base=0.25secondsretry_backoff_max=4.0secondsretry_jitter=0.1(10% random jitter)Retry-Afterheader is honored when present
Configure it:
from loops_py import LoopsClient
client = LoopsClient(
api_key="loops_api_key",
max_retries=5,
retry_backoff_base=0.2,
retry_backoff_max=6.0,
retry_jitter=0.2,
)
Disable retries by setting max_retries=0.
Endpoint mapping
contactscreate_contact->POST /contacts/createupdate_contact->POST /contacts/updatefind_contact->GET /contacts/finddelete_contact->POST /contacts/deletecreate_contact_property->POST /contacts/propertieslist_contact_properties->GET /contacts/properties
mailing_listslist_mailing_lists->GET /lists
eventssend_event->POST /events/send
transactionalsend_transactional_email->POST /transactionallist_transactional_emails->GET /transactional
accountverify_api_key->GET /api-keylist_dedicated_sending_ips->GET /dedicated-sending-ips
Idempotency support
For endpoints that support idempotency, you can optionally pass idempotency_key:
client.events.send_event(
{"email": "user@example.com", "eventName": "signup"},
idempotency_key="signup-user@example.com-2026-02-28",
)
Build and publish
Build sdist + wheel:
uv build
Artifacts:
dist/*.tar.gzdist/*.whl
Publish (requires PyPI token):
export UV_PUBLISH_TOKEN="pypi-..."
uv publish
TestPyPI:
uv publish --publish-url https://test.pypi.org/legacy/
Development
uv run ruff check .
uv run pytest
License
MIT
Metadata
Release files for pyloops-so 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyloops_so-0.1.5.tar.gz | 49.6 kB | With provenance | Details for pyloops_so-0.1.5.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyloops_so-0.1.5-py3-none-any.whl | Python 3 | none | any | Details for pyloops_so-0.1.5-py3-none-any.whl |
Total release size: 64.5 kB
Release files / pyloops_so-0.1.5.tar.gz
| Download URL | pyloops_so-0.1.5.tar.gz |
|---|---|
| Size | 49.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c7ab6fd79707da0e854b227d5acc25886f72e6747ca36be7dffd07d4029f8740
|
|
BLAKE2b-256 checksum How to use checksums |
8af5b1fbb6d95cd2cc1baee44334c55d159d98476fa734bf7a0551121ad7a121
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 28, 2026.
Transparency logRelease files / pyloops_so-0.1.5-py3-none-any.whl
| Download URL | pyloops_so-0.1.5-py3-none-any.whl |
|---|---|
| Size | 15.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4486b0ff9fb0797d3d0ec1c5948a79347dc9a6bb81177cfd4cc41f3034108f97
|
|
BLAKE2b-256 checksum How to use checksums |
e600edbd4c606708200d256898c1e18e3229ed9ad970de1855794bc79a0d16f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 28, 2026.
Transparency logSecurity for pyloops-so 0.1.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pyloops_so-0.1.5.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 28, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.