everpod-ai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Everpod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: everpod-ai
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Everpod API.
5
+ Author-email: Everpod <support@everpod.ai>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://everpod.ai
8
+ Project-URL: Documentation, https://everpod.ai/docs/api
9
+ Project-URL: Repository, https://github.com/everpod-ai/sdk-python
10
+ Project-URL: Issues, https://github.com/everpod-ai/sdk-python/issues
11
+ Keywords: everpod,ai-agents,agent-hosting,openclaw,mcp,sdk
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ # Everpod SDK for Python
18
+
19
+ The official Python client for the [Everpod API](https://everpod.ai/docs/api).
20
+
21
+ Everpod runs an AI agent on a pod: a private, always-on cloud computer of its own. A key lets an agent or an app you trust see your pods and start a new one for you, which you then pay for on everpod.ai. It can't pay, change or cancel a plan, delete anything, or open your agent's control panel.
22
+
23
+ If you are connecting an agent such as Claude Code or Codex, you do not need this library: point it at Everpod's MCP server, as the [API reference](https://everpod.ai/docs/api) shows.
24
+
25
+ ## Install
26
+
27
+ ```
28
+ pip install everpod-ai
29
+ ```
30
+
31
+ Python 3.9 or later. No dependencies.
32
+
33
+ ## Use
34
+
35
+ To make a key, open [everpod.ai/account/keys](https://everpod.ai/account/keys) and sign in with your email address and the code we send you. If you have no account yet, signing in makes one. Give the key to the library from an environment variable.
36
+
37
+ ```python
38
+ import os
39
+
40
+ from everpod_ai import Everpod
41
+
42
+ everpod = Everpod(os.environ["EVERPOD_API_KEY"])
43
+
44
+ # Start a pod under the name you want for its agent. Nothing is charged:
45
+ # the pod stays unpaid until you open pay_url in a browser and pay there.
46
+ pod = everpod.start_pod("Otto")
47
+ print(pod["status"], pod["pay_url"])
48
+
49
+ # The pods on your account, oldest first.
50
+ pods = everpod.list_pods()
51
+
52
+ # One pod by its id: how to check whether it has been paid for, and whether it is ready.
53
+ same = everpod.get_pod(pod["id"])
54
+ ```
55
+
56
+ While your account has an unpaid pod, starting another returns that same pod, renamed if the name differs.
57
+
58
+ A pod's fields and what each status means are in the [API reference](https://everpod.ai/docs/api).
59
+
60
+ ## When a request is refused
61
+
62
+ A refusal raises an `EverpodError`. Its `message` is the API's own sentence, which says what happened and what to do next; `code` and `status` are the refusal's.
63
+
64
+ ```python
65
+ from everpod_ai import EverpodError
66
+
67
+ try:
68
+ everpod.get_pod(pod_id)
69
+ except EverpodError as error:
70
+ print(error.code, error.status, error.message)
71
+ ```
72
+
73
+ ## Questions
74
+
75
+ [support@everpod.ai](mailto:support@everpod.ai)
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,63 @@
1
+ # Everpod SDK for Python
2
+
3
+ The official Python client for the [Everpod API](https://everpod.ai/docs/api).
4
+
5
+ Everpod runs an AI agent on a pod: a private, always-on cloud computer of its own. A key lets an agent or an app you trust see your pods and start a new one for you, which you then pay for on everpod.ai. It can't pay, change or cancel a plan, delete anything, or open your agent's control panel.
6
+
7
+ If you are connecting an agent such as Claude Code or Codex, you do not need this library: point it at Everpod's MCP server, as the [API reference](https://everpod.ai/docs/api) shows.
8
+
9
+ ## Install
10
+
11
+ ```
12
+ pip install everpod-ai
13
+ ```
14
+
15
+ Python 3.9 or later. No dependencies.
16
+
17
+ ## Use
18
+
19
+ To make a key, open [everpod.ai/account/keys](https://everpod.ai/account/keys) and sign in with your email address and the code we send you. If you have no account yet, signing in makes one. Give the key to the library from an environment variable.
20
+
21
+ ```python
22
+ import os
23
+
24
+ from everpod_ai import Everpod
25
+
26
+ everpod = Everpod(os.environ["EVERPOD_API_KEY"])
27
+
28
+ # Start a pod under the name you want for its agent. Nothing is charged:
29
+ # the pod stays unpaid until you open pay_url in a browser and pay there.
30
+ pod = everpod.start_pod("Otto")
31
+ print(pod["status"], pod["pay_url"])
32
+
33
+ # The pods on your account, oldest first.
34
+ pods = everpod.list_pods()
35
+
36
+ # One pod by its id: how to check whether it has been paid for, and whether it is ready.
37
+ same = everpod.get_pod(pod["id"])
38
+ ```
39
+
40
+ While your account has an unpaid pod, starting another returns that same pod, renamed if the name differs.
41
+
42
+ A pod's fields and what each status means are in the [API reference](https://everpod.ai/docs/api).
43
+
44
+ ## When a request is refused
45
+
46
+ A refusal raises an `EverpodError`. Its `message` is the API's own sentence, which says what happened and what to do next; `code` and `status` are the refusal's.
47
+
48
+ ```python
49
+ from everpod_ai import EverpodError
50
+
51
+ try:
52
+ everpod.get_pod(pod_id)
53
+ except EverpodError as error:
54
+ print(error.code, error.status, error.message)
55
+ ```
56
+
57
+ ## Questions
58
+
59
+ [support@everpod.ai](mailto:support@everpod.ai)
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "everpod-ai"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the Everpod API."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Everpod", email = "support@everpod.ai" },
12
+ ]
13
+ license = "MIT"
14
+ requires-python = ">=3.9"
15
+ dependencies = []
16
+ keywords = ["everpod", "ai-agents", "agent-hosting", "openclaw", "mcp", "sdk"]
17
+
18
+ [project.urls]
19
+ Homepage = "https://everpod.ai"
20
+ Documentation = "https://everpod.ai/docs/api"
21
+ Repository = "https://github.com/everpod-ai/sdk-python"
22
+ Issues = "https://github.com/everpod-ai/sdk-python/issues"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ [tool.setuptools.package-data]
28
+ everpod_ai = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,124 @@
1
+ """The Everpod API's Python client: the operations a key can call
2
+ (https://everpod.ai/docs/api) and nothing else. Standard library only.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import urllib.error
9
+ import urllib.parse
10
+ import urllib.request
11
+ from typing import Any, Callable, List, Optional, TypedDict
12
+
13
+ __version__ = "0.1.0"
14
+ __all__ = ["Everpod", "EverpodError", "Pod", "PodSubscription", "__version__"]
15
+
16
+ DEFAULT_BASE_URL = "https://everpod.ai"
17
+
18
+
19
+ class PodSubscription(TypedDict):
20
+ status: str
21
+ current_period_end: Optional[str] # when the paid period ends
22
+ cancel_at_period_end: bool # whether the subscription is set to end then
23
+
24
+
25
+ class Pod(TypedDict):
26
+ """A pod. What each status means: https://everpod.ai/docs/api"""
27
+
28
+ id: str
29
+ name: str # the name of the pod's agent
30
+ harness: str # the agent software the pod runs
31
+ status: str # awaiting_payment, building, setup_delayed, ready, needs_attention, stopped
32
+ created_at: str
33
+ url: Optional[str] # the pod's page on everpod.ai; None until the pod is paid for
34
+ pay_url: Optional[str] # where the pod's owner pays, while the status is awaiting_payment
35
+ plan: Optional[str] # None until the pod is paid for
36
+ subscription: Optional[PodSubscription] # None until the pod is paid for
37
+
38
+
39
+ class EverpodError(Exception):
40
+ """What the API refused, or what stopped the request reaching it.
41
+
42
+ ``message`` is the API's own sentence, written to be passed on to the
43
+ account's owner; ``code`` and ``status`` are the refusal's
44
+ (https://everpod.ai/docs/api).
45
+ """
46
+
47
+ def __init__(self, message: str, *, code: Optional[str] = None, status: Optional[int] = None):
48
+ super().__init__(message)
49
+ self.message = message
50
+ self.code = code
51
+ self.status = status
52
+
53
+
54
+ class Everpod:
55
+ def __init__(
56
+ self,
57
+ api_key: str,
58
+ *,
59
+ base_url: str = DEFAULT_BASE_URL,
60
+ timeout: float = 30.0,
61
+ opener: Optional[Callable[..., Any]] = None,
62
+ ):
63
+ if not api_key:
64
+ raise EverpodError(
65
+ "An Everpod API key is needed: Everpod(api_key=...). The account's owner "
66
+ "makes one at https://everpod.ai/account/keys.",
67
+ code="missing_api_key",
68
+ )
69
+ self._api_key = api_key
70
+ self._base_url = base_url.rstrip("/")
71
+ self._timeout = timeout
72
+ self._open = opener or urllib.request.urlopen
73
+
74
+ def list_pods(self) -> List[Pod]:
75
+ """The pods on the account, oldest first."""
76
+ return self._request("GET", "/pods")["pods"]
77
+
78
+ def get_pod(self, pod_id: str) -> Pod:
79
+ """One pod by its id."""
80
+ return self._request("GET", "/pods/" + urllib.parse.quote(str(pod_id), safe=""))["pod"]
81
+
82
+ def start_pod(self, name: str) -> Pod:
83
+ """Start a pod under the name its owner wants for their agent.
84
+
85
+ Nothing is charged: the pod stays unpaid until its owner pays at
86
+ ``pay_url``.
87
+ """
88
+ return self._request("POST", "/pods", {"name": name})["pod"]
89
+
90
+ def _request(self, method: str, path: str, body: Optional[dict] = None) -> Any:
91
+ headers = {
92
+ "Authorization": f"Bearer {self._api_key}",
93
+ "Accept": "application/json",
94
+ "User-Agent": f"everpod-sdk-python/{__version__}",
95
+ }
96
+ data = None
97
+ if body is not None:
98
+ headers["Content-Type"] = "application/json"
99
+ data = json.dumps(body).encode("utf-8")
100
+ request = urllib.request.Request(
101
+ f"{self._base_url}/api/v1{path}", data=data, headers=headers, method=method
102
+ )
103
+ try:
104
+ with self._open(request, timeout=self._timeout) as response:
105
+ return json.loads(response.read().decode("utf-8"))
106
+ except urllib.error.HTTPError as refused:
107
+ try:
108
+ error = json.loads(refused.read().decode("utf-8")).get("error")
109
+ except Exception:
110
+ error = None
111
+ # The API's refusals carry a code and a sentence; anything else that
112
+ # answers in its place is reported by its status alone.
113
+ if not isinstance(error, dict):
114
+ error = {}
115
+ raise EverpodError(
116
+ error.get("message") or f"Everpod answered {refused.code}.",
117
+ code=error.get("code") or "http_error",
118
+ status=refused.code,
119
+ ) from None
120
+ except (urllib.error.URLError, OSError) as failed:
121
+ reason = getattr(failed, "reason", failed)
122
+ raise EverpodError(
123
+ f"The request to Everpod did not complete: {reason}", code="network_error"
124
+ ) from failed
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: everpod-ai
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Everpod API.
5
+ Author-email: Everpod <support@everpod.ai>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://everpod.ai
8
+ Project-URL: Documentation, https://everpod.ai/docs/api
9
+ Project-URL: Repository, https://github.com/everpod-ai/sdk-python
10
+ Project-URL: Issues, https://github.com/everpod-ai/sdk-python/issues
11
+ Keywords: everpod,ai-agents,agent-hosting,openclaw,mcp,sdk
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ # Everpod SDK for Python
18
+
19
+ The official Python client for the [Everpod API](https://everpod.ai/docs/api).
20
+
21
+ Everpod runs an AI agent on a pod: a private, always-on cloud computer of its own. A key lets an agent or an app you trust see your pods and start a new one for you, which you then pay for on everpod.ai. It can't pay, change or cancel a plan, delete anything, or open your agent's control panel.
22
+
23
+ If you are connecting an agent such as Claude Code or Codex, you do not need this library: point it at Everpod's MCP server, as the [API reference](https://everpod.ai/docs/api) shows.
24
+
25
+ ## Install
26
+
27
+ ```
28
+ pip install everpod-ai
29
+ ```
30
+
31
+ Python 3.9 or later. No dependencies.
32
+
33
+ ## Use
34
+
35
+ To make a key, open [everpod.ai/account/keys](https://everpod.ai/account/keys) and sign in with your email address and the code we send you. If you have no account yet, signing in makes one. Give the key to the library from an environment variable.
36
+
37
+ ```python
38
+ import os
39
+
40
+ from everpod_ai import Everpod
41
+
42
+ everpod = Everpod(os.environ["EVERPOD_API_KEY"])
43
+
44
+ # Start a pod under the name you want for its agent. Nothing is charged:
45
+ # the pod stays unpaid until you open pay_url in a browser and pay there.
46
+ pod = everpod.start_pod("Otto")
47
+ print(pod["status"], pod["pay_url"])
48
+
49
+ # The pods on your account, oldest first.
50
+ pods = everpod.list_pods()
51
+
52
+ # One pod by its id: how to check whether it has been paid for, and whether it is ready.
53
+ same = everpod.get_pod(pod["id"])
54
+ ```
55
+
56
+ While your account has an unpaid pod, starting another returns that same pod, renamed if the name differs.
57
+
58
+ A pod's fields and what each status means are in the [API reference](https://everpod.ai/docs/api).
59
+
60
+ ## When a request is refused
61
+
62
+ A refusal raises an `EverpodError`. Its `message` is the API's own sentence, which says what happened and what to do next; `code` and `status` are the refusal's.
63
+
64
+ ```python
65
+ from everpod_ai import EverpodError
66
+
67
+ try:
68
+ everpod.get_pod(pod_id)
69
+ except EverpodError as error:
70
+ print(error.code, error.status, error.message)
71
+ ```
72
+
73
+ ## Questions
74
+
75
+ [support@everpod.ai](mailto:support@everpod.ai)
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/everpod_ai/__init__.py
5
+ src/everpod_ai/py.typed
6
+ src/everpod_ai.egg-info/PKG-INFO
7
+ src/everpod_ai.egg-info/SOURCES.txt
8
+ src/everpod_ai.egg-info/dependency_links.txt
9
+ src/everpod_ai.egg-info/top_level.txt
10
+ tests/test_client.py
@@ -0,0 +1 @@
1
+ everpod_ai
@@ -0,0 +1,136 @@
1
+ """Unit tests for the client: what it sends and how it reads what comes back,
2
+ against an opener that answers from a script. Nothing here reaches a network.
3
+
4
+ python tests/test_client.py
5
+ """
6
+
7
+ import io
8
+ import json
9
+ import os
10
+ import sys
11
+ import unittest
12
+ import urllib.error
13
+
14
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
15
+
16
+ from everpod_ai import Everpod, EverpodError, __version__ # noqa: E402
17
+
18
+ POD = {
19
+ "id": "5f0c1a52-8f1e-4d0b-9a57-3f6f2f7c1e9a",
20
+ "name": "Otto",
21
+ "harness": "openclaw",
22
+ "status": "awaiting_payment",
23
+ "created_at": "2026-10-03T09:12:44.512345+00:00",
24
+ "url": None,
25
+ "pay_url": "https://everpod.ai/create?pod=5f0c1a52-8f1e-4d0b-9a57-3f6f2f7c1e9a",
26
+ "plan": None,
27
+ "subscription": None,
28
+ }
29
+
30
+
31
+ class Answer(io.BytesIO):
32
+ """A response the client can use as a context manager."""
33
+
34
+ def __enter__(self):
35
+ return self
36
+
37
+ def __exit__(self, *exc):
38
+ return False
39
+
40
+
41
+ class Scripted:
42
+ """An opener that records each request and answers ``status`` with ``body``."""
43
+
44
+ def __init__(self, status, body):
45
+ self.status = status
46
+ self.raw = body if isinstance(body, bytes) else json.dumps(body).encode("utf-8")
47
+ self.calls = []
48
+
49
+ def __call__(self, request, timeout=None):
50
+ self.calls.append((request, timeout))
51
+ if self.status >= 400:
52
+ raise urllib.error.HTTPError(
53
+ request.full_url, self.status, "refused", hdrs=None, fp=io.BytesIO(self.raw)
54
+ )
55
+ return Answer(self.raw)
56
+
57
+
58
+ def client(opener, **extra):
59
+ return Everpod("everpod_test", opener=opener, **extra)
60
+
61
+
62
+ class ClientTests(unittest.TestCase):
63
+ def test_no_key_says_where_a_key_is_made(self):
64
+ with self.assertRaises(EverpodError) as raised:
65
+ Everpod("")
66
+ self.assertEqual(raised.exception.code, "missing_api_key")
67
+ self.assertIn("/account/keys", raised.exception.message)
68
+
69
+ def test_list_pods_asks_the_list_route_with_the_key(self):
70
+ opener = Scripted(200, {"pods": [POD]})
71
+ self.assertEqual(client(opener).list_pods(), [POD])
72
+ request, timeout = opener.calls[0]
73
+ self.assertEqual(request.full_url, "https://everpod.ai/api/v1/pods")
74
+ self.assertEqual(request.get_method(), "GET")
75
+ self.assertEqual(request.get_header("Authorization"), "Bearer everpod_test")
76
+ self.assertEqual(request.get_header("User-agent"), f"everpod-sdk-python/{__version__}")
77
+ self.assertIsNone(request.data)
78
+ self.assertEqual(timeout, 30.0)
79
+
80
+ def test_get_pod_escapes_its_id(self):
81
+ opener = Scripted(200, {"pod": POD})
82
+ self.assertEqual(client(opener).get_pod(POD["id"]), POD)
83
+ self.assertEqual(opener.calls[0][0].full_url, f"https://everpod.ai/api/v1/pods/{POD['id']}")
84
+ client(opener).get_pod("a/b")
85
+ self.assertEqual(opener.calls[1][0].full_url, "https://everpod.ai/api/v1/pods/a%2Fb")
86
+
87
+ def test_start_pod_posts_the_name_as_json(self):
88
+ opener = Scripted(201, {"pod": POD})
89
+ self.assertEqual(client(opener).start_pod("Otto"), POD)
90
+ request = opener.calls[0][0]
91
+ self.assertEqual(request.get_method(), "POST")
92
+ self.assertEqual(request.get_header("Content-type"), "application/json")
93
+ self.assertEqual(json.loads(request.data.decode("utf-8")), {"name": "Otto"})
94
+
95
+ def test_a_base_url_with_a_trailing_slash(self):
96
+ opener = Scripted(200, {"pods": []})
97
+ client(opener, base_url="http://localhost:3111/").list_pods()
98
+ self.assertEqual(opener.calls[0][0].full_url, "http://localhost:3111/api/v1/pods")
99
+
100
+ def test_a_refusal_carries_the_apis_code_status_and_sentence(self):
101
+ opener = Scripted(
102
+ 404, {"error": {"code": "not_found", "message": "No pod with that id on this account."}}
103
+ )
104
+ with self.assertRaises(EverpodError) as raised:
105
+ client(opener).get_pod("nope")
106
+ self.assertEqual(raised.exception.code, "not_found")
107
+ self.assertEqual(raised.exception.status, 404)
108
+ self.assertEqual(raised.exception.message, "No pod with that id on this account.")
109
+
110
+ def test_an_answer_that_is_not_the_apis_json_still_says_its_status(self):
111
+ opener = Scripted(502, b"<html>bad gateway</html>")
112
+ with self.assertRaises(EverpodError) as raised:
113
+ client(opener).list_pods()
114
+ self.assertEqual(raised.exception.code, "http_error")
115
+ self.assertEqual(raised.exception.status, 502)
116
+ self.assertIn("502", raised.exception.message)
117
+
118
+ def test_a_json_answer_of_another_shape_still_says_its_status(self):
119
+ opener = Scripted(403, {"ok": False, "error": "forbidden"})
120
+ with self.assertRaises(EverpodError) as raised:
121
+ client(opener).list_pods()
122
+ self.assertEqual(raised.exception.code, "http_error")
123
+ self.assertEqual(raised.exception.status, 403)
124
+
125
+ def test_a_request_that_never_completes_is_a_network_error(self):
126
+ def failing(request, timeout=None):
127
+ raise urllib.error.URLError("no route to host")
128
+
129
+ with self.assertRaises(EverpodError) as raised:
130
+ client(failing).list_pods()
131
+ self.assertEqual(raised.exception.code, "network_error")
132
+ self.assertIsInstance(raised.exception.__cause__, urllib.error.URLError)
133
+
134
+
135
+ if __name__ == "__main__":
136
+ unittest.main()