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
  • 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

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

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.3.tar.gz (47.6 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.3-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyloops_so-0.1.3.tar.gz
  • Upload date:
  • Size: 47.6 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.3.tar.gz
Algorithm Hash digest
SHA256 e2e6ccba0ae394dae3f226d83e3c7073be702679dc95317dc97bb4d741998de0
MD5 446d8ac3ceb2c6fc56393f037936d1a1
BLAKE2b-256 fa9f3936cbfb753418f620851674a6c882addee196493b7b7b4aa363844b39b7

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyloops_so-0.1.3.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.3-py3-none-any.whl.

File metadata

  • Download URL: pyloops_so-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 14.7 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 08be5d8728ee8d73e70439712b3010834cfdfc6d36b567aba104d46c92376c0b
MD5 8a23b432211d3385ec794be19667eeda
BLAKE2b-256 4d11c358d0921cf5a68e12d91b7a5effd89f39c1a4448216ef9dbf93eba55ebb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyloops_so-0.1.3-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