Metadata-Version: 2.5
Name: neuctra-authix
Version: 2.1.0
Summary: Python SDK for Neuctra Authix — authentication, identity, and user data as a service.
Project-URL: Homepage, https://authix.neuctra.com
Project-URL: Documentation, https://authix.neuctra.com/docs
Project-URL: Source, https://github.com/neuctra/neuctra-authix
Author: Neuctra
License: MIT
License-File: LICENSE
Keywords: auth,authentication,authix,identity,neuctra,sdk,user-management
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: requests>=2.25.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.23; extra == 'dev'
Description-Content-Type: text/markdown

# Neuctra Authix — Python SDK

Authentication, identity, and user data as a service.

```bash
pip install neuctra-authix
```

## Quick start

```python
import os
from neuctra_authix import Authix

authix = Authix(
    api_key=os.environ["AUTHIX_SECRET_KEY"],  # sk_live_… — server only
    app_id=os.environ["AUTHIX_APP_ID"],
)

user = authix.signup_user(
    name="Ada Lovelace",
    email="ada@example.com",
    password="a-strong-password",
)

authix.add_user_data(
    user_id=user["user"]["id"],
    data_category="notes",
    data={"title": "First note", "body": "Hello world"},
)
```

## API keys

Authix issues two kinds of key, both passed as `api_key`. Create them on the
API Keys page of the dashboard (Type: Secret or Publishable); either kind can
be limited to one app, and is then refused for any other (403 `KEY_APP_MISMATCH`).

- **Secret key** (`sk_live_…`) — full account authority: any user by id,
  listing and searching users, writing app data, setting roles and status.
  Python normally runs on your server, so this is the key to use. Load it from
  an environment variable and never embed it in a shipped client. Authix
  refuses a secret key sent from a browser (403 `SECRET_KEY_IN_BROWSER`).
  Older `ak_live_…` keys are secret keys.
- **Publishable key** (`pk_live_…`) — for a Python program that runs on an end
  user's machine (desktop app, CLI). It can run sign-in flows, read app data,
  and reach only the signed-in user's own profile and records; any `user_id`
  you pass is ignored in favour of the session. It cannot list or read other
  users, write app data, or set role, `isActive` or `packageInfo`.

With a secret key, `user_id` is whatever you pass — take it from a session
your server has verified, never from the request.

## Pagination

Every list and search returns a bounded `Page` — 20 records by default, 100
maximum. There is no "fetch everything" mode, because one such request against a
large app would have to load the whole dataset into memory.

```python
page = authix.get_user_data(user_id="u_1", limit=50)

for record in page:          # a Page iterates its records
    print(record["title"])

page.has_more                # bool
page.next_cursor             # pass back as cursor=... for the next page
```

To walk everything, use the `iter_*` helpers — they follow the cursor for you:

```python
for record in authix.iter_user_data(user_id="u_1"):
    process(record)

for user in authix.iter_all_users():
    print(user["email"])

# Bound the traversal when the dataset size is unknown
for record in authix.iter_all_users_data(q="invoice", max_pages=10):
    process(record)
```

## Searching

`keys` is an exact-match containment query; `q` is a substring match. Both are
index-backed in the database, not filtered in Python.

```python
authix.search_in_user_data(user_id="u_1", keys={"status": "paid"})
authix.search_in_user_data(user_id="u_1", q="invoice")
authix.search_in_all_app_users_data(keys={"status": "paid"}, limit=100)
```

User search is restricted to a fixed field list — `id`, `username`, `name`,
`email`, `phone`, `address`, `role`, `isVerified`, `isActive`. Filtering on
anything else raises `ValidationError`; credential columns are never searchable.

## Concurrency

Pass the `version` you last read to avoid clobbering someone else's write:

```python
from neuctra_authix import VersionConflictError

record = authix.get_single_user_data(user_id="u_1", data_id="d_1")

try:
    authix.update_user_data(
        user_id="u_1",
        data_id="d_1",
        data={"status": "shipped"},
        version=record["data"]["version"],
    )
except VersionConflictError as exc:
    print("changed since read; now at version", exc.current_version)
```

## Atomic batches

When records must change together, use `batch()` — every operation commits or
none do:

```python
authix.batch(user_id="u_1", operations=[
    {"type": "create", "dataCategory": "orders", "total": 40},
    {"type": "update", "id": stock_id, "version": 3, "remaining": 9},
    {"type": "delete", "id": draft_id},
])
```

A version mismatch or missing record aborts the whole batch and nothing is
written. Maximum 50 operations.

## Share links

Let anyone read one record by link, without signing in:

```python
link = authix.share_user_data(user_id="u_1", data_id=note_id)  # expires_in_days=7, rotate=True
url = f"https://myapp.com/s/{link['data']['shareToken']}"

note = authix.get_shared_data(token=token)["data"]  # NotFoundError if revoked/expired
authix.unshare_user_data(user_id="u_1", data_id=note_id)
```

## Errors

Every failure raises a typed exception, so you can catch what you care about:

```python
from neuctra_authix import (
    AuthixError,             # base — catches everything below
    ConfigurationError,      # bad client setup; raised before any request
    AuthenticationError,     # 401 — bad, expired, or revoked credential
    NoUserSessionError,      # 401 — publishable key, no live user session
    InsufficientScopeError,  # 403 — publishable key on a secret-only endpoint
    PermissionDeniedError,   # 403 — see .code, e.g. KEY_APP_MISMATCH
    NotFoundError,           # 404
    ValidationError,         # 400 — includes non-searchable field rejections
    VersionConflictError,    # 409 — optimistic concurrency
    RateLimitError,          # 429 — rate limited or monthly quota exhausted
    ServerError,             # 5xx
    NetworkError,            # connection failed or timed out
)
```

`InsufficientScopeError` (code `INSUFFICIENT_SCOPE`) is a publishable key on an
endpoint that needs a secret key; it carries a `.hint` explaining the fix.
`PermissionDeniedError.code` names other refusals: `SECRET_KEY_IN_BROWSER`,
`ORIGINS_NOT_CONFIGURED`, `KEY_APP_MISMATCH`, an unverified account, or a plan
limit.

## Sessions

The client holds a `requests.Session`, so a cookie from `login_user()` persists
and later calls act as that user:

```python
authix.login_user(email="ada@example.com", password="…")
authix.check_user_session()   # {"authenticated": True, "user": {...}}
authix.logout_user()
```

Use it as a context manager to close the connection pool deterministically:

```python
with Authix(api_key=..., app_id=...) as authix:
    authix.get_user(id="u_1")
```

## Configuration

```python
Authix(
    app_id="app_...",              # required
    api_key="sk_live_...",         # secret key on a server; pk_live_... in a shipped client
    base_url="https://server.authix.neuctra.com/api",
    app_name=None,                 # optional label
    timeout=30.0,                  # seconds
    session=None,                  # bring your own requests.Session
)
```

## API reference

**End-user auth** — `signup_user`, `login_user`, `logout_user`,
`check_user_session`, `change_password`, `request_email_verification_otp`,
`verify_email`, `request_reset_user_password_otp`, `reset_user_password`

**Users** — `get_user_profile`, `update_user`, `delete_user`;
`get_user`, `check_if_user_exists` *(secret key only)*

**User records** — `add_user_data`, `get_user_data`, `iter_user_data`,
`get_single_user_data`, `search_in_user_data`, `update_user_data`,
`delete_user_data`, `delete_many_user_data`, `batch`, `share_user_data`,
`unshare_user_data`, `get_shared_data`

**App-wide records** — `get_app_data`, `iter_app_data`, `get_single_app_data`,
`search_in_app_data` (any key); `add_app_data`, `update_app_data`,
`delete_app_data` *(secret key only)*

**Across all users** *(secret key only)* — `get_all_users_from_app`,
`iter_all_users`, `search_in_all_app_users`, `get_all_users_data_from_app`,
`search_in_all_app_users_data`, `iter_all_users_data`

## Development

```bash
pip install -e ".[dev]"
pytest
```

## Links

- [Documentation](https://authix.neuctra.com/docs)
- [Dashboard](https://authix.neuctra.com/dashboard)

MIT licensed.
