Skip to main content

Lightweight Python SDK (unofficial) for the Loops API

Project description

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.

[!IMPORTANT] Read the full API guide before integrating: API Guide

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

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.5.tar.gz (49.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.5-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyloops_so-0.1.5.tar.gz
  • Upload date:
  • Size: 49.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.5.tar.gz
Algorithm Hash digest
SHA256 c7ab6fd79707da0e854b227d5acc25886f72e6747ca36be7dffd07d4029f8740
MD5 b144fce74e250b61ab1b0462ab00e405
BLAKE2b-256 8af5b1fbb6d95cd2cc1baee44334c55d159d98476fa734bf7a0551121ad7a121

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: pyloops_so-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 15.0 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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 4486b0ff9fb0797d3d0ec1c5948a79347dc9a6bb81177cfd4cc41f3034108f97
MD5 912d33dcd1c6238307e14ff00202fe44
BLAKE2b-256 e600edbd4c606708200d256898c1e18e3229ed9ad970de1855794bc79a0d16f7

See more details on using hashes here.

Provenance

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