|
| 1 | +# siftfy-python |
| 2 | + |
| 3 | +Official Python client for the [Siftfy](https://siftfy.io) spam-classification |
| 4 | +API. POST text, get a calibrated spam probability back. One round-trip, no |
| 5 | +queues, no models to host. |
| 6 | + |
| 7 | +[](https://pypi.org/project/siftfy/) |
| 8 | +[](https://pypi.org/project/siftfy/) |
| 9 | +[](LICENSE) |
| 10 | + |
| 11 | +## Install |
| 12 | + |
| 13 | +```bash |
| 14 | +pip install siftfy |
| 15 | +``` |
| 16 | + |
| 17 | +## Quick start |
| 18 | + |
| 19 | +```python |
| 20 | +from siftfy import Siftfy |
| 21 | + |
| 22 | +client = Siftfy(api_key="sk_live_...") |
| 23 | + |
| 24 | +result = client.predict("Win a free iPad — click here!") |
| 25 | +print(result.spam_probability) # 0.97 |
| 26 | +print(result.likelihood) # "high" |
| 27 | +``` |
| 28 | + |
| 29 | +Get an API key at [siftfy.io](https://siftfy.io) — the free tier covers |
| 30 | +10,000 requests/month at no cost. |
| 31 | + |
| 32 | +## Async |
| 33 | + |
| 34 | +```python |
| 35 | +import asyncio |
| 36 | +from siftfy import AsyncSiftfy |
| 37 | + |
| 38 | +async def main() -> None: |
| 39 | + async with AsyncSiftfy(api_key="sk_live_...") as client: |
| 40 | + result = await client.predict("hello, world") |
| 41 | + print(result.spam_probability) |
| 42 | + |
| 43 | +asyncio.run(main()) |
| 44 | +``` |
| 45 | + |
| 46 | +## Calibrated probabilities |
| 47 | + |
| 48 | +Every `spam_probability` is a calibrated value between 0 and 1 — at 0.7, |
| 49 | +roughly 70% of inputs with that score are actually spam. Pick a threshold |
| 50 | +appropriate to your use case (a help-desk form tolerates more false positives |
| 51 | +than a marketplace listing); the same model serves both. |
| 52 | + |
| 53 | +The `likelihood` field is a coarse bucket (`"low"`, `"medium"`, `"high"`) |
| 54 | +derived from the probability. Handy for quick branches, but for production |
| 55 | +decisions thread on the raw probability and your own threshold. |
| 56 | + |
| 57 | +## Errors |
| 58 | + |
| 59 | +```python |
| 60 | +from siftfy import ( |
| 61 | + Siftfy, |
| 62 | + AuthenticationError, # 401 — bad / revoked key |
| 63 | + RateLimitError, # 429 — over your tier limit; .retry_after available |
| 64 | + APIError, # any other 4xx/5xx |
| 65 | + SiftfyError, # network / request transport errors |
| 66 | +) |
| 67 | + |
| 68 | +try: |
| 69 | + result = client.predict(text) |
| 70 | +except RateLimitError as e: |
| 71 | + sleep_for = e.retry_after or 1.0 |
| 72 | + ... |
| 73 | +except AuthenticationError: |
| 74 | + ... |
| 75 | +except APIError as e: |
| 76 | + log(f"siftfy error {e.status_code}: {e} (request_id={e.request_id})") |
| 77 | +``` |
| 78 | + |
| 79 | +The client retries idempotent failures (HTTP 408 / 429 / 5xx, network errors) |
| 80 | +with exponential backoff and jitter, honouring `Retry-After` when present. |
| 81 | +Tune with `max_retries=N` (default 2; set 0 to disable). |
| 82 | + |
| 83 | +## Configuration |
| 84 | + |
| 85 | +```python |
| 86 | +client = Siftfy( |
| 87 | + api_key="sk_live_...", |
| 88 | + base_url="https://api.siftfy.io", # override for self-hosted / staging |
| 89 | + timeout=10.0, # seconds, applied per attempt |
| 90 | + max_retries=2, # 0 disables retries |
| 91 | +) |
| 92 | +``` |
| 93 | + |
| 94 | +You can also pass your own `httpx.Client` (or `httpx.AsyncClient` for the |
| 95 | +async client) via `http_client=...` if you want connection pooling, custom |
| 96 | +transports, or to share a client across services. |
| 97 | + |
| 98 | +## Resources |
| 99 | + |
| 100 | +- API reference: <https://siftfy.io/docs> |
| 101 | +- Pricing: <https://siftfy.io/pricing> |
| 102 | +- Status: ping `https://api.siftfy.io/health` |
| 103 | +- Issues: <https://github.com/GipsyChef/siftfy-python/issues> |
| 104 | + |
| 105 | +## License |
| 106 | + |
| 107 | +MIT — see [LICENSE](LICENSE). |
0 commit comments