Metadata-Version: 2.4
Name: velocity-os
Version: 0.1.0
Summary: Official Python SDK for the Velocity OS Extension API
Author-email: DEFINEXTEND LTD t/a High-Velocity X <raihan@highvelocitystartups.com>
Maintainer-email: Raihan Islam <raihan@highvelocitystartups.com>
License: MIT License
        
        Copyright (c) 2026 DEFINEXTEND LTD t/a High-Velocity X
        
        Velocity OS and the velocity-os Python SDK are products of DEFINEXTEND LTD,
        trading as High-Velocity X. Authored by Raihan Islam, founder of High-Velocity X
        (raihan@highvelocitystartups.com).
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://app.velocityos.app
Project-URL: Documentation, https://app.velocityos.app/docs/extension-api
Project-URL: Source, https://github.com/velocityos/velocity-os
Keywords: velocity-os,sdk,extensions,api,webhooks
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1.0,>=0.24.0
Dynamic: license-file

# velocity-os

Official Python SDK for the Velocity OS Extension API.

```bash
pip install velocity-os
```

Build third-party plugins (CRM syncs, accountability apps, industry-specific
tools, etc.) without writing HTTP boilerplate. The SDK gives you typed
methods, automatic retry, scope pre-checks, and a decorator-based webhook
router.

```python
from velocity_os import VelocityOS

vos = VelocityOS(
    api_key=os.environ["VOS_EXT_KEY"],
    webhook_secret=os.environ["VOS_WEBHOOK_SECRET"],
)

me = vos.whoami()
print(me.user.email, me.extension.granted_scopes)

tasks = vos.tasks.list()
for t in tasks.open:
    print(t.action, t.deadline)

vos.tasks.create(
    action="Schedule weekly review",
    module_source="L12",
    quadrant="schedule",
)
```

---

## Install

```bash
pip install velocity-os
```

Requires Python 3.9+.

For local development inside the Velocity OS monorepo:

```bash
pip install -e sdk/python
```

---

## Authentication

You need two things from your Velocity OS developer dashboard:

1. **An install API key** (`vos_ext_…`) — generated when a Velocity OS user
   installs your extension. One key per (user, extension) pair.
2. **A webhook signing secret** (`whsec_…`) — generated when you register
   your extension. Used to verify inbound webhooks. Optional if your
   extension only reads data.

```python
vos = VelocityOS(
    api_key="vos_ext_...",
    webhook_secret="whsec_...",  # optional, only needed for webhooks
)
```

---

## Available scopes

Each extension declares the scopes it needs at registration. Users grant
a subset (or all) of those scopes when they install. Calls to endpoints
the extension wasn't granted access to raise `ScopeMissingError`
**before** the network call (after the first `whoami()` populates the
scope cache).

| Scope | What it grants |
|---|---|
| `read:profile` | Founder profile fields |
| `read:modules` | Outputs of completed modules |
| `read:offers` | Approved/active offers |
| `read:team` | Team roster |
| `read:partnerships` | Partnerships list |
| `read:tasks` | Action backlog (open + completed) |
| `read:academy` | Academy programs (academy tier only) |
| `write:tasks` | Append/update tasks |
| `write:entities` | Add team / partnerships |
| `write:notes` | Append notes |

---

## Resource reference

All resources are accessed as attributes of the `VelocityOS` client.
Each method docstring states which scope is required.

### `vos.whoami()` → `WhoAmI`

Returns the user this key acts on behalf of and the extension metadata.
Cached for the lifetime of the client; pass `refresh=True` to re-fetch.

```python
me = vos.whoami()
me.user.email           # 'user@example.com'
me.user.tier            # 'growth'
me.extension.name       # 'GHL Sync'
me.extension.granted_scopes  # ['read:tasks', 'write:tasks', ...]
me.sandbox              # True if dev sandbox mode is on
```

### `vos.profile.get()` → `dict`

Returns the user's whitelisted founder profile fields. **Scope:** `read:profile`

### `vos.modules.list()` → `list[ModuleSummary]`

Returns a summary of every module the user has touched. **Scope:** `read:modules`

```python
for m in vos.modules.list():
    print(m.code, m.status, m.completed_at)
```

### `vos.modules.get(code)` → `Module`

Returns the parsed JSON output for one module (e.g. `"F6"`, `"C3"`).
Raises `NotFoundError` if the user has not completed it. **Scope:** `read:modules`

```python
f6 = vos.modules.get("F6")
print(f6.output["pricing_model"])
```

### `vos.offers.list()` → `list[Offer]`

Returns approved/active offers from the user's entity registry. **Scope:** `read:offers`

### `vos.team.list()` → `list[TeamMember]`

Returns the user's team roster. **Scope:** `read:team`

### `vos.team.add(name, role="", email="")` → `TeamMember`

Appends a new team member. **Scope:** `write:entities`

### `vos.partnerships.list()` → `list[Partnership]`

Returns the user's partnerships. **Scope:** `read:partnerships`

### `vos.tasks.list()` → `TaskList`

Returns combined open and completed actions. **Scope:** `read:tasks`

```python
tasks = vos.tasks.list()
for t in tasks.open:
    print(t.action, t.quadrant, t.deadline)
for c in tasks.completed:
    print(c.action, c.completed_at)
```

### `vos.tasks.create(action, module_source, **kwargs)` → `Task`

Appends a new action to the user's backlog. Triggers a `tasks.created`
webhook to subscribed extensions. **Scope:** `write:tasks`

```python
vos.tasks.create(
    action="Schedule weekly review with COO",
    module_source="L12",
    category="people",
    timeframe="this_week",
    quadrant="schedule",
    deadline="2026-04-20",
    assignee="Alex",
)
```

### `vos.academy.programs()` → `list[AcademyProgram]`

Returns academy programs owned by the user. **Scope:** `read:academy`
(populated only for academy tier).

---

## Webhooks

If you registered a `webhook_url` in your developer dashboard, Velocity OS
will POST signed events to that URL when relevant things happen in any
installed user's account. The SDK ships with a decorator-based router and
automatic HMAC-SHA256 signature verification.

```python
handler = vos.webhook_handler()

@handler.on("tasks.created")
def on_task(payload):
    print("new task:", payload["action"])

@handler.on("tasks.updated")
def on_task_changed(payload):
    print("updated:", payload["action"], "->", payload["quadrant"])

@handler.on("module.updated")
def on_module(payload):
    invalidate_cache(payload["module_code"])

@handler.on("subscription.changed")
def on_sub(payload):
    print("user is now on tier:", payload["new_tier"])

@handler.on("*")  # catch-all, runs in addition to specific handlers
def on_any(envelope):
    log_event(envelope["event_type"], envelope["delivery_id"])
```

### Wiring it up

The handler is framework-agnostic — it takes a headers mapping and the
raw request body bytes. Here's Flask:

```python
from flask import request

@app.post("/webhook")
def webhook():
    result, status = handler.handle(request.headers, request.get_data())
    return result, status
```

FastAPI:

```python
from fastapi import Request

@app.post("/webhook")
async def webhook(request: Request):
    body = await request.body()
    result, status = handler.handle(request.headers, body)
    return JSONResponse(result, status_code=status)
```

The SDK verifies the `X-VOS-Signature` header BEFORE calling any of your
handlers. Requests with bad, missing, or expired signatures (>5 minutes
old by default) return `(401, ...)` and never invoke your code.

### Event types

| Event | Required scope on the install | Payload shape |
|---|---|---|
| `module.updated` | `read:modules` | `{module_code, completed_at}` |
| `tasks.created` | `read:tasks` | `{module_source, action, category, timeframe}` |
| `tasks.updated` | `read:tasks` | `{module_source, action, quadrant, assignee, deadline}` |
| `team.added` | `read:team` | `{name, role, email}` |
| `subscription.changed` | `read:profile` | `{new_tier, source}` |
| `extension.installed` | `read:profile` | `{extension_slug, scopes}` |
| `extension.revoked` | `read:profile` | `{install_id}` |

### Manual signature verification

If you can't use the SDK's handler (e.g. you're routing through a
different server), the standalone helper is exported:

```python
from velocity_os import verify_signature

if not verify_signature(secret, signature_header, raw_body):
    abort(401)
```

---

## Errors

All SDK errors inherit from `VelocityOSError`. Catch the specific subclass
you care about:

```python
from velocity_os import (
    AuthenticationError,    # 401, key invalid/revoked
    ScopeMissingError,      # 403, missing required scope
    NotFoundError,          # 404, resource doesn't exist
    RateLimitError,         # 429, rate limited
    ServerError,            # 5xx, after retries exhausted
    APIError,               # base for any other API error
)

try:
    vos.tasks.create(action="...", module_source="L12")
except ScopeMissingError as exc:
    print("missing scope:", exc.scope)
except AuthenticationError:
    print("user revoked the key, ask them to reinstall")
```

The SDK retries network errors and 5xx responses with exponential backoff
(0.5s, 1.5s, 4s) before giving up. Auth/scope errors fail immediately so
you don't waste retries on a revoked key.

---

## Local pre-flight scope checks

After the first `vos.whoami()` call, the SDK caches your install's
granted scopes. Subsequent resource calls check the cache locally before
making the network call:

```python
vos = VelocityOS(api_key="vos_ext_...", ...)
vos.whoami()  # populates scope cache

vos.tasks.create(action="...", module_source="L12")
# If write:tasks isn't in your granted scopes, raises ScopeMissingError
# WITHOUT making a network call.
```

If you skip `whoami()` entirely, the SDK falls back to letting the server
return `403 scope_required`, which the SDK still translates into the same
`ScopeMissingError` exception — same UX, just one extra round-trip.

---

## Sandbox mode

If you're a developer testing your own extension on yourself, toggle
"sandbox mode" on in your developer dashboard. Calls to the API using
your own keys will then return deterministic dummy data instead of your
real account state. The `me.sandbox` field on `whoami()` and the
`Response.sandbox` flag will be `True` so you can detect it.

This only affects YOUR keys. Other users who installed your extension
hit their real data.

---

## User-Agent

The SDK sets a `User-Agent: VelocityOS-Python-SDK/0.1.0` header on every
request so Velocity OS server logs can identify SDK traffic. Override
with `user_agent=` if you want to brand your extension:

```python
vos = VelocityOS(
    api_key="vos_ext_...",
    user_agent="ghl-sync/2.4.0",
)
```

---

## Version

```python
from velocity_os import __version__
print(__version__)  # '0.1.0'
```

---

## See also

- [`docs/extension-api.md`](../../docs/extension-api.md) — raw HTTP API reference
- [`examples/sample-extension/`](../../examples/sample-extension/) — a complete Flask
  app using this SDK
