Skip to main content

plasgos-crm-sdk (Python)

Official Python SDK for Plasgos CRM: API Integrasi v3. The SDK covers:

  • omnichannel messages (WhatsApp official and unofficial, Instagram, Messenger)
  • contacts and leads
  • broadcasts
  • followups
  • signed webhooks

Features:

  • Python 3.9+, one dependency (httpx), fully typed (py.typed, mypy strict).
  • Automatic request signing that covers the method, path, query and body.
  • An automatic Idempotency-Key on every POST, reused on retries.
  • Typed errors carrying the API error_code.
  • A Page type with auto_paging().
  • Webhook HMAC verification.

Upgrading from 1.x? See UPGRADING.md. The v1 client lives on as the deprecated LegacyClient (PlasgosCrmClient is an alias); its docs are in LEGACY_V1.md.

Install

pip install plasgos-crm-sdk

Quick start

import os

from plasgos_crm import Client, Content, PlasgosApiError

with Client(
    os.environ["PLASGOS_API_KEY"],
    os.environ["PLASGOS_SECRET_KEY"],
    base_url="sandbox",  # "production" by default; or an absolute URL
) as client:
    me = client.me.get()
    print(me.data["tenant"]["name"])

    try:
        sent = client.messages.send(
            {
                "endpoint_id": "6281100000001",
                "contact": {"phone_number": "081234567890", "name": "Budi Santoso"},  # profile only for a NEW contact
                "content": Content.template("66f1a2b3c4d5e6f708192a3b")
                .dynamic("name", "{{full_name}}")
                .custom("order", "INV-2026-0917"),
            },
            idempotency_key="wc-1234-processing",  # one per intent
        )
        print(sent.data["status"])  # pending | sent | failed (202)
    except PlasgosApiError as error:
        # Branch on the stable code; show error.message (Indonesian) to people.
        if error.error_code == "requires_template":
            ...
        raise

    for contact in client.contacts.list(per_page=100).auto_paging():
        ...  # every page fetched lazily

    client.contacts.upsert({"phone_number": "081234567890", "name": "Budi", "tags": ["woocommerce"]})
    client.leads.create({"contact": {"phone_number": "081234567890"}, "pipeline_id": "pl_1"})
    client.followups.enroll("fu_1", {"contact": {"id": "ct_1"}})

asyncio

AsyncClient has the same resources, methods and parameters (each method is async; lists return an AsyncPage):

from plasgos_crm import AsyncClient

async with AsyncClient(api_key, secret_key, base_url="sandbox") as client:
    me = await client.me.get()
    page = await client.contacts.list(per_page=100)
    async for contact in page.auto_paging():
        ...

It takes an httpx.AsyncBaseTransport / httpx.AsyncClient and an async sleeper; close it with async with or await client.aclose().

Resources:

  • me, channels
  • endpoints (including templates), template_variables
  • messages (send_options, send, get)
  • contacts, contact_groups
  • pipelines, leads
  • broadcasts, followups
  • webhooks (including rotate_secret)

Query parameters may be a mapping, keyword arguments, or both: client.contacts.list({"tag": "vip"}, per_page=100). client.request(method, path, query=None, body=None, idempotency_key=None, *, options=None) is an escape hatch for endpoints that do not have a method yet. The full surface is in docs/SDK_SPEC.md §3. Inputs and outputs use the API wire format (snake_case), described by openapi/openapi-v3.json.

Options

Argument Default Meaning
base_url "production" "production", "sandbox", or an absolute URL (host only).
timeout 15.0 s Timeout for each attempt.
max_retries 2 Automatic retries (see below).
max_retry_wait 30.0 s Longest Retry-After honoured automatically.
user_agent plasgos-crm-sdk-python/<version> python/<version> Integrations append themselves.
transport / http_client — Inject an httpx transport (e.g. httpx.MockTransport) or your own httpx.Client.
sleeper, clock real Test hooks.

Logging: enable DEBUG on the plasgos_crm logger for one record per attempt (method, path without the query, status, duration, request id); headers and bodies are never logged.

Every method also takes keyword-only per-call options (docs/SDK_SPEC.md §1.1):

client.messages.send(body, "wc-1234-processing", options={"timeout": 5, "max_retries": 0})
client.contacts.get(contact_id, options={"headers": {"X-Trace-Id": trace_id}})

Responses

r.http_status  # 200, 201, 202 …
r.message      # user-facing text
r.data         # payload
r.meta         # e.g. {"created": True} (201) / {"created": False} (200)
r.request_id   # quote it when contacting Plasgos
r.rate_limit   # RateLimit(limit=120, remaining=119, reset=42)
r.replayed     # True when the server replayed the first response for this Idempotency-Key

List methods return a Page: items, pagination, has_next_page(), next_page(), auto_paging() (generator); it is iterable over the current page and supports len().

Request bodies, query parameters and data are typed with TypedDicts generated from the OpenAPI spec (checked by mypy/pyright; plain dicts at runtime):

from plasgos_crm.v3 import types as t

sent = client.messages.send({"endpoint_id": "…", "contact": {"phone_number": "0812…"}, "content": Content.text("Halo")})
status: t.MessageStatus = sent.data["status"]
page = client.contacts.list({"tag": "vip"})  # Page[t.Contact]

Errors

PlasgosApiError
├─ AuthenticationError            401
│  └─ SignatureExpiredError       401 signature_expired (check the server clock)
├─ PermissionError                403 (scope_missing, api_key_revoked, …)
│  └─ PlanError                   403 not_entitled
├─ NotFoundError                  404
├─ ConflictError                  409 (idempotency_in_progress, identity_taken, …)
├─ BadRequestError                400
├─ ValidationError                422 / 400 with fields → .fields
├─ RateLimitError                 429 → .retry_after (rate_limited | daily_quota_exceeded)
├─ ServerError                    5xx
│  ├─ ChannelError                502 provider error
│  └─ ServiceUnavailableError     503
└─ TransportError                 no response

Every error exposes error_code, error_type, details, fields, doc_url, request_id and retry_after (plus the legacy errors view [{message, path}]). PermissionError deliberately shadows the builtin inside this package; import it as from plasgos_crm import PermissionError as PlasgosPermissionError if that bothers you.

Retries. Transport errors, 502/503 and rate_limited (for any method) are retried automatically up to max_retries, reusing the same idempotency key. A plain 500 is retried only for GET. Other 4xx errors and daily_quota_exceeded are never retried.

Webhooks

from plasgos_crm import Webhook, WebhookEventType, WebhookVerificationError

# Flask
@app.post("/webhooks/plasgos")
def plasgos_webhook():
    try:
        event = Webhook.verify(request.get_data(), request.headers, signing_secret)
        # during secret rotation: Webhook.verify(body, headers, [new_secret, previous_secret])
    except WebhookVerificationError:
        return "", 401
    if event["type"] == WebhookEventType.MESSAGE_RECEIVED:
        ...  # event["data"] is a Message
    return "", 200

headers may be any mapping, including a WSGI environ or Django request.META (HTTP_* keys). Verify the raw body. Deliveries can repeat, so drop event["id"] values you have already processed.

Webchat identity token

Let the website chat widget recognise a signed-in customer. Sign on your server with the widget's identity secret (wcs_…) — never in the browser — and pass only the token to the page:

from plasgos_crm import Webchat

identity_token = Webchat.sign_identity(
    os.environ["PLASGOS_WEBCHAT_IDENTITY_SECRET"],
    {"sub": str(user.id), "email": user.email, "name": user.name},
)  # keyword options: widget_id=, ttl= (seconds, default 3600, max 86400)
# template: Plasgos("boot", { widgetId: "wgt_…", identityToken: {{ identity_token|tojson }} })

Tokens are single-use per visitor: sign a fresh one per page render / sign-in.

Development

pip install -e ".[dev]"
pytest              # tests/test_v3_live.py runs only with PLASGOS_LIVE_BASE_URL/_API_KEY/_SECRET_KEY
mypy src
ruff check .

Metadata

Release files for plasgos-crm-sdk 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for plasgos-crm-sdk 2.1.0
File Size Uploaded Provenance Actions
plasgos_crm_sdk-2.1.0.tar.gz 52.2 kB With provenance Details for plasgos_crm_sdk-2.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for plasgos-crm-sdk 2.1.0
File Interpreter ABI Platform Actions
plasgos_crm_sdk-2.1.0-py3-none-any.whl Python 3 none any Details for plasgos_crm_sdk-2.1.0-py3-none-any.whl

Total release size: 106.4 kB

Release files / plasgos_crm_sdk-2.1.0.tar.gz

Download URL plasgos_crm_sdk-2.1.0.tar.gz
Size 52.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f88ec6280314dcd7c06d9cecc7a8877d4152b87bf3d10b486c635da17e5c27ec
BLAKE2b-256 checksum
How to use checksums
7a61f319bcd0c95c6c7f4aac28f1166b3663f1550b3a928365fea0fdc66e378d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 27, 2026.

Transparency log

Release files / plasgos_crm_sdk-2.1.0-py3-none-any.whl

Download URL plasgos_crm_sdk-2.1.0-py3-none-any.whl
Size 54.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
43b6d2e33fdeaca54d3a631770bccd989bab14266ca353fcd10df605f6187071
BLAKE2b-256 checksum
How to use checksums
240ff7da98ca9f9ec39b94307cc58b4f0494cc714757c265a42ba39a39830943
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 27, 2026.

Transparency log

Security for plasgos-crm-sdk 2.1.0

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

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for plasgos_crm_sdk-2.1.0.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 Oct 27, 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.

Release history Release notifications | RSS feed

This release

2.1.0 This release

2 release files Full provenance

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