Skip to main content

Lightweight Python SDK (unofficial) for the Loops API

Project description

Unofficial Loops.so Python Library (pyloops-so)

pyloops-so is a 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
  • Single runtime dependency (pydantic)
  • 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

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")

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 -> PUT /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 /mailing-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, 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyloops_so-0.1.2.tar.gz (35.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyloops_so-0.1.2-py3-none-any.whl (14.3 kB view details)

Uploaded Python 3

File details

Details for the file pyloops_so-0.1.2.tar.gz.

File metadata

  • Download URL: pyloops_so-0.1.2.tar.gz
  • Upload date:
  • Size: 35.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyloops_so-0.1.2.tar.gz
Algorithm Hash digest
SHA256 397dee59df97d0abb06fd6311900f70b4ef238efefb26f85d49594a12e662028
MD5 d9b1ecd8e0d252b5debb3950ddd7ca62
BLAKE2b-256 09f8a477e7a748a73871f9a37bd4cbcf1d629614c8c8d34a594a2b300577922e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyloops_so-0.1.2.tar.gz:

Publisher: publish.yml on annjawn/loops-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyloops_so-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: pyloops_so-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 14.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyloops_so-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 67e0a9867c3d1bbb4f08963deab5a0fc0c7a74dd76a7a80880d49ef10a832d4e
MD5 765d03207480e4cc8b60c908906fe9a6
BLAKE2b-256 e2fda28b943c322362c17040fd63f21ce2a9ac616b69e3d41a60d27fdc00ae90

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyloops_so-0.1.2-py3-none-any.whl:

Publisher: publish.yml on annjawn/loops-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page