Skip to main content

Unofficial Loops.so Python Library (pyloops-so)

PyPI version CI Python versions

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:

Installation

Install from PyPI:

uv add pyloops-so

For local development:

uv sync --extra dev

Examples

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:

  1. Top-level convenience methods (client.create_contact(...)) for compatibility.
  2. 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.25 seconds
  • retry_backoff_max=4.0 seconds
  • retry_jitter=0.1 (10% random jitter)
  • Retry-After header 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

  • contacts
    • create_contact -> POST /contacts/create
    • update_contact -> POST /contacts/update
    • find_contact -> GET /contacts/find
    • delete_contact -> POST /contacts/delete
    • create_contact_property -> POST /contacts/properties
    • list_contact_properties -> GET /contacts/properties
  • mailing_lists
    • list_mailing_lists -> GET /lists
  • events
    • send_event -> POST /events/send
  • transactional
    • send_transactional_email -> POST /transactional
    • list_transactional_emails -> GET /transactional
  • account
    • verify_api_key -> GET /api-key
    • list_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.gz
  • dist/*.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)

Source distribution for pyloops-so 0.1.5
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)

Table of built distributions (wheels) for pyloops-so 0.1.5
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page