Skip to content

decisa-ai/decisa-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Decisa Python SDK

Official Python SDK for the Decisa server-side ingest API — record conversions and events from your backend with typed inputs, a built-in money guard, automatic retries, and typed errors.

Server-side only. The API key is a secret. Never put it in client-side code, a mobile app, or a browser bundle — it belongs in your backend environment variables.

Install

pip install decisa

Requires Python 3.9+. The only runtime dependency is httpx.

Quickstart (sync) — record a sale

from decisa import Decisa, cents

# Read the key from the environment — never hard-code it.
with Decisa(api_key="dcs_ak_your_key_here") as decisa:
    result = decisa.conversions.record(
        type="sale",
        external_id="order_1001",        # your order id — the idempotency key
        value_cents=cents(19.99),        # -> 1999; the wire field is integer cents
        currency="BRL",
        customer_email="buyer@example.com",  # hashed server-side, raw never stored
        method="pix",
    )

    print(result.id)          # "conv_..."
    print(result.attributed)  # True / False
    print(result.data)        # typed conversion dict
    print(result.raw)         # full { data, meta, error } envelope

value_cents is integer cents only — passing a float like 19.99 raises a DecisaError before anything is sent. Use the cents() helper to convert a decimal amount safely (it rounds half-up and is immune to binary-float artefacts, so cents(19.99) == 1999).

Recording an event

from decisa import Decisa

with Decisa(api_key="dcs_ak_your_key_here") as decisa:
    result = decisa.events.record(
        event_id="evt_checkout_98f3a1c2",   # 8..64 chars — the idempotency key
        event_name="Purchase",
        value_cents=1999,
        currency="BRL",
        # pixel_keys accepts a list; it's joined to a comma-separated string
        # on the wire automatically (this differs from conversions).
        pixel_keys=["px_main", "px_secondary"],
        url="https://shop.example.com/thank-you",
    )
    print(result.status)      # "recorded"
    print(result.event_name)  # "Purchase"

Async client

AsyncDecisa mirrors the sync client exactly — same methods, same arguments, same results — only await-ed:

import asyncio
from decisa import AsyncDecisa, cents

async def main() -> None:
    async with AsyncDecisa(api_key="dcs_ak_your_key_here") as decisa:
        result = await decisa.conversions.record(
            type="sale",
            external_id="order_2002",
            value_cents=cents(49.90),
            currency="USD",
        )
        print(result.id)

asyncio.run(main())

Errors

Every failure maps to a typed exception under the DecisaError base class, so you can handle each case precisely:

from decisa import (
    Decisa,
    DecisaError,
    DecisaValidationError,
    DecisaAuthError,
    DecisaForbiddenError,
    DecisaRateLimitError,
    DecisaServerError,
)

try:
    with Decisa(api_key="dcs_ak_your_key_here") as decisa:
        decisa.conversions.record(type="sale", external_id="order_1001")
except DecisaValidationError as err:        # HTTP 422
    print(err.fields)        # { "currency": ["The currency must be 3 characters."] }
except DecisaAuthError as err:              # HTTP 401 — bad/missing/revoked key
    print(err.code)          # "API_KEY_INVALID"
except DecisaForbiddenError as err:         # HTTP 403
    # err.code distinguishes the two 403 cases:
    #   "INSUFFICIENT_SCOPE"  -> the key lacks conversions:write / events:write
    #   "ENTITLEMENT_MISSING" -> the workspace hasn't enabled attribution
    print(err.code)
except DecisaRateLimitError as err:         # HTTP 429
    print(err.retry_after)   # seconds from the Retry-After header, or None
except DecisaServerError as err:            # HTTP 5xx (already retried)
    print(err.status_code)
except DecisaError as err:                  # anything else, plus input guards
    print(err.message)

Every error carries status_code, code, message, and request_id (read from X-Request-Id / X-Decisa-Request-Id, falling back to meta.request_id) to make support tickets easy. The API key is never included in any error message or in repr() of the client.

Client-side guards (raised before any request)

These deterministic mistakes are caught instantly, with no network round-trip:

  • value_cents is not a real int (a float, bool, or decimal is rejected — use cents()),
  • external_id / event_id missing or empty,
  • type / event_name not in the allowed set,
  • both pixel_key and pixel_keys set (they are mutually exclusive).

Retries

Transient failures are retried automatically with exponential backoff and full jitter:

  • Retries only on network errors, 5xx, and 429.
  • Never retries other 4xx — those are deterministic client errors.
  • Honors the Retry-After header on 429.
  • Defaults: max_retries=2, timeout=10.0 seconds. Tune both per client:
Decisa(api_key="dcs_ak_...", timeout=5.0, max_retries=4)

Idempotency

Re-sending a record with the same external_id (conversions) or event_id (events) is safe. The server dedupes on it, returns the stored record, and still responds 201 / 202 — a duplicate is not an error. This is what makes retries safe; you can replay a failed batch without double-counting.

Allowed values

from decisa import CONVERSION_TYPES, EVENT_NAMES

# CONVERSION_TYPES:
#   signup, trial_start, subscription_start, sale, lead, app_install, custom, refund
# EVENT_NAMES:
#   PageView, ViewContent, Search, AddToCart, AddPaymentInfo, InitiateCheckout,
#   Lead, CompleteRegistration, Purchase, StartTrial, Subscribe, AppInstall, Custom

ConversionType and EventName are exported as typing.Literal unions for static checking; ConversionInput / EventInput are TypedDicts you can build and pass positionally (decisa.conversions.record({...})) or spread as keyword arguments.

License

MIT — see LICENSE.

About

Official Python SDK for the Decisa ingest API (conversions + events), sync + async via httpx

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages