Metadata-Version: 2.5
Name: getstack
Version: 1.4.0
Summary: Python SDK for STACK — trust infrastructure for AI agents
Project-URL: Homepage, https://getstack.run
Project-URL: Documentation, https://getstack.run/docs/sdk
Project-URL: Repository, https://github.com/getstack-run/sdk-python
Author-email: STACK <hello@getstack.run>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,ai,mcp,passport,stack,trust
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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
Requires-Python: >=3.9
Requires-Dist: cbor2>=5.6.0
Requires-Dist: cryptography>=42.0.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: pyjwt>=2.8.0
Description-Content-Type: text/markdown

# getstack

Python SDK for [STACK](https://getstack.run) — trust infrastructure for AI agents.

## Install

```bash
pip install getstack
```

## Quick start

Sign in once on your machine — the SDK reads credentials from `~/.stack/credentials.json` automatically:

```bash
npx -y @getstackrun/cli auth login
```

```python
import hashlib
import json
from getstack.errors import StackError


def run_fleet_mission_example(stack, request, provider_url="https://slack.com/api/chat.postMessage"):
    started = stack.missions.start(request)
    repeated = stack.missions.start(request)
    assert repeated["mission_id"] == started["mission_id"]
    operation_id = hashlib.sha256(json.dumps([started["mission_id"], "support-update"]).encode()).hexdigest()
    response = stack.proxy.request("slack", provider_url, "POST",
        body={"channel": "C123", "text": "Support update"},
        passport_token=started["passport"]["token"], authority_request_id=operation_id)
    assert response.status == 200
    try:
        stack.proxy.request("slack", provider_url, "POST", body={"channel": "OTHER", "text": "Support update"},
            passport_token=started["passport"]["token"], authority_request_id=operation_id + "_denied")
    except StackError as error:
        assert error.status_code == 403
    else:
        raise AssertionError("Expected channel restriction")
    before = stack.missions.get(started["mission_id"])
    renewed = stack.missions.renew(started["mission_id"])
    after = stack.missions.get(renewed["mission_id"])
    assert renewed["mission_id"] == started["mission_id"]
    assert after["fleet"]["limits"] == before["fleet"]["limits"]
    assert after["fleet"]["usage"] == before["fleet"]["usage"]
    completed = stack.missions.complete(started["mission_id"], output={"done": True})
    assert completed["terminal_state"] == "completed_successful"
    stopped = stack.missions.start({**request, "idempotency_key": request["idempotency_key"] + "-stop"})
    stack.missions.revoke(stopped["mission_id"], reason="operator_initiated_clean")
    try:
        stack.missions.renew(stopped["mission_id"])
    except StackError:
        pass
    else:
        raise AssertionError("Expected revoked Mission")
    return {"mission_id": started["mission_id"], "operation_id": operation_id, "completed": True, "revoked": True}
```

## Authentication

Four sources, resolved in priority order:

```python
# 1. Explicit auth strategy
stack = Stack.from_oauth(client_id="...", client_secret="", access_token="...", refresh_token="...")
stack = Stack.from_session(session_token="...")

# 2. agent_id (Phase 2 — recommended for production runtimes)
stack = Stack(agent_id="agt_xxx")

# 3. api_key (legacy sk_live_*; for CI without a browser)
stack = Stack(api_key="sk_live_...")
# or set STACK_API_KEY in the environment

# 4. ~/.stack/credentials.json (Phase 1 — `stack-cli auth login` writes it)
stack = Stack()
```

See [/docs/security/stack-auth](https://getstack.run/docs/security/stack-auth) for the full auth model and [/docs/security/agent-keys](https://getstack.run/docs/security/agent-keys) for the per-agent keypair story.

## Renew a Mission's Passport

```python
renewed = stack.missions.renew(mission_id)
passport_token = renewed["passport"]["token"]
```

Renewal keeps the same Mission, permission and recorded usage. Its deadline and limits still apply. Start a new Mission for the next job.

## Services

```python
# Agents
stack.agents.register(name, description=None, accountability_mode="enforced")
stack.agents.get(agent_id)
stack.agents.list()
stack.agents.update(agent_id, **fields)
stack.agents.unblock(agent_id)

# Passports
stack.passports.issue(agent_id, intent=None, services=None, ...)
stack.passports.verify(token)
stack.passports.revoke(jti, reason=None)
stack.passports.checkpoint(jti, services_used, actions_count, ...)
stack.passports.checkout(jti, services_used, actions_count, ...)
stack.passports.report(jti)

# Credentials
stack.credentials.get(provider)
stack.credentials.get_by_connection(connection_id)

# Services
stack.services.list()
stack.services.connect_custom(name, credential, ...)
stack.services.verify(connection_id)
stack.services.disconnect(connection_id)

# Reviews
stack.reviews.list(status="flagged")
stack.reviews.decide(checkout_id, decision, notes=None, block_future=False)

# Drop-offs
stack.dropoffs.create(from_agent_id, to_agent_id, schema, ...)
stack.dropoffs.deposit(dropoff_id, data, agent_id)
stack.dropoffs.collect(dropoff_id, agent_id)

# Notifications
stack.notifications.list()
stack.notifications.create(channel_type, destination, ...)
stack.notifications.delete(channel_id)

# Audit
stack.audit.list(page=1, limit=50, action=None, agent_id=None)
```

## Links

- [Documentation](https://getstack.run/docs/sdk)
- [Dashboard](https://getstack.run)
- [GitHub](https://github.com/getstack-run/sdk-python)
