Metadata-Version: 2.4
Name: 1scribe
Version: 2.1.0
Summary: 1Scribe: verify signed access documents, activate products, and vendor server helpers
Author: 1Scribe
License: Apache-2.0
Keywords: licensing,license-key,entitlements,sdk
Classifier: License :: OSI Approved :: Apache Software License
Requires-Python: >=3.9.2
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=50.0.2
Dynamic: license-file

# 1scribe (Python)

```sh
pip install 1scribe
```

The PyPI package is `1scribe`; the import name is `onescribe`, because a Python module name cannot start with a digit.

Python form of the 1Scribe SDK packages `@1scribe/core`, `@1scribe/client` and `@1scribe/server`.
Same checks, same platform calls, same result reasons; held to the shared conformance vectors in
`packages/1scribe-conformance/vectors.json`. Unpublished (publishing waits on TL-05).

| Module             | TypeScript twin   | What it does                                                                  |
| ------------------ | ----------------- | ----------------------------------------------------------------------------- |
| `onescribe`        | `@1scribe/core`   | `verify_access_document`, `canonicalize` (offline verification)               |
| `onescribe.client` | `@1scribe/client` | `OneScribeClient` (code, activate, refresh, machine), `LicenseSession`, store |
| `onescribe.server` | `@1scribe/server` | `verify_signature`, `receive_code_delivery`, `VendorApiClient`                |

Runtime dependency: `cryptography` (core only). HTTP and HMAC use the standard library.

## Product (installed app)

```python
from onescribe.client import DeviceInfo, FileLicenseStore, LicenseSession, OneScribeClient, get_or_create_device_id

client = OneScribeClient(base_url="https://agentskope.com")
device = DeviceInfo(id=get_or_create_device_id(app_dir / "device-id"), reference_name="Front desk PC")
code = client.request_code(key=license_key, email=email, device=device)
result = client.activate(challenge_id=code.challenge_id, code=typed_code, device=device)

session = LicenseSession(base_url="https://agentskope.com", product_id=PRODUCT_ID, client=client,
                         store=FileLicenseStore(app_dir / "license.json"), device_id=device.id)
if result.ok:
    session.save(result.document, result.keys)
decision = session.check()  # offline; refreshes only when past its grace
```

Calls never raise for network problems: they return `reason="unavailable"`.

## Vendor backend

```python
from onescribe.server import VendorApiClient, receive_code_delivery

delivery = receive_code_delivery(signature_header=request.headers.get("X-Scribe-Signature"),
                                 raw_body=request.get_data(), secret=CODE_DELIVERY_SECRET)
vendor = VendorApiClient(base_url="https://agentskope.com", api_key=os.environ["SCRIBE_API_KEY"])
access = vendor.vouched_access(product_id=PRODUCT_ID, subject_id=user.id)
```

Pass the raw request bytes to the signature check, never re-serialised JSON.

## Base URL rule (both languages)

`base_url` is optional and defaults to `https://agentskope.com` (`DEFAULT_PLATFORM_URL`); the same platform is also reachable at `https://1scribe.xyz` (`ALTERNATE_PLATFORM_URL`). When given, it must be `https://` for any host. Plain `http://` is allowed only for the exact loopback
hosts `localhost`, `127.0.0.1` and `[::1]` (local development). Anything else raises `ValueError`
(`TypeError` in TypeScript) before a request is made: remote http, userinfo (`user:pass@`),
look-alike hosts such as `localhost.evil.com`, backslashes, whitespace, query, fragment, bad ports
and Unicode host names (pass the `xn--` form). `OneScribeClient`, `LicenseSession` and
`VendorApiClient` all apply it, and the default transport re-checks every URL. Otherwise anyone on
the network could read the license key, code or vendor key, and replace the signed access document
or the trusted keys.

## Differences from the TypeScript packages

- Synchronous API (standard-library `urllib`); inject any `transport` callable for async stacks or proxies.
- The default transport never follows redirects, refuses non-HTTP schemes and plain http to a
  non-loopback host, caps responses at 2 MiB and
  times out after 15 s.
- `VendorApiClient.request` raises `ValueError` for a path that does not start with a single `/` or
  an unknown method, so the API key cannot be sent to another host.
- Signature header `t` must be decimal digits (`1e9` or `-5` are `malformed_header`).
- Malformed success answers from the platform are `unavailable` instead of passed through.

## Tests

```
python -m pytest packages/onescribe-py      # needs: pip install cryptography pytest
```
