Official Python SDK for the Velocity OS Extension API
Project description
velocity-os
Official Python SDK for the Velocity OS Extension API.
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.
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
pip install velocity-os
Requires Python 3.9+.
For local development inside the Velocity OS monorepo:
pip install -e sdk/python
Authentication
You need two things from your Velocity OS developer dashboard:
- An install API key (
vos_ext_…) — generated when a Velocity OS user installs your extension. One key per (user, extension) pair. - A webhook signing secret (
whsec_…) — generated when you register your extension. Used to verify inbound webhooks. Optional if your extension only reads data.
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.
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
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
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
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
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.
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:
from flask import request
@app.post("/webhook")
def webhook():
result, status = handler.handle(request.headers, request.get_data())
return result, status
FastAPI:
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:
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:
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:
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:
vos = VelocityOS(
api_key="vos_ext_...",
user_agent="ghl-sync/2.4.0",
)
Version
from velocity_os import __version__
print(__version__) # '0.1.0'
See also
docs/extension-api.md— raw HTTP API referenceexamples/sample-extension/— a complete Flask app using this SDK
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file velocity_os-0.1.0.tar.gz.
File metadata
- Download URL: velocity_os-0.1.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed1e24932ac2fcb4d71d2a8800eb0957500fefd0e7b7c40e11361c73893b89d6
|
|
| MD5 |
c25a362ac00c1bcf60db7691a5ebc920
|
|
| BLAKE2b-256 |
380ad4cc7e595f7545b40d79fc3dca75024acfe7f52145c7a1d156020507d7fc
|
File details
Details for the file velocity_os-0.1.0-py3-none-any.whl.
File metadata
- Download URL: velocity_os-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a4d97f0d7396ab2a17cd18a417bcbb7cf85c0ba22a3f0fe52c3f3d5afe77212
|
|
| MD5 |
3b5b7ddb65a18aa3abd499b32589bc5d
|
|
| BLAKE2b-256 |
7a3993bcd09c3c1eb10cec93a178748da105eff9d5954f60f46e9c4ad10d46bb
|