Skip to content

Commit c542cfa

Browse files
George-iamgeobon
andauthored
feat: add invites create/get/accept helpers to Python SDK (#9)
Expose invites.create, invites.get, and invites.accept methods with test coverage and quickstart usage so GA SDK parity closes the invites family batch. Made-with: Cursor Co-authored-by: George-iam <georgeb@gmail.com>
1 parent 934918a commit c542cfa

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ with AxmeClient(config) as client:
5151
print(approval["approval"]["decision"])
5252
capabilities = client.get_capabilities()
5353
print(capabilities["supported_intent_types"])
54+
invite = client.create_invite(
55+
{"owner_agent": "agent://example/receiver", "recipient_hint": "Partner A", "ttl_seconds": 3600},
56+
idempotency_key="invite-create-001",
57+
)
58+
print(invite["token"])
59+
invite_details = client.get_invite(invite["token"])
60+
print(invite_details["status"])
61+
accepted = client.accept_invite(
62+
invite["token"],
63+
{"nick": "@PartnerA.User", "display_name": "Partner A"},
64+
idempotency_key="invite-accept-001",
65+
)
66+
print(accepted["public_address"])
5467
subscription = client.upsert_webhook_subscription(
5568
{
5669
"callback_url": "https://integrator.example/webhooks/axme",

axme_sdk/client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,42 @@ def decide_approval(
162162
def get_capabilities(self, *, trace_id: str | None = None) -> dict[str, Any]:
163163
return self._request_json("GET", "/v1/capabilities", trace_id=trace_id, retryable=True)
164164

165+
def create_invite(
166+
self,
167+
payload: dict[str, Any],
168+
*,
169+
idempotency_key: str | None = None,
170+
trace_id: str | None = None,
171+
) -> dict[str, Any]:
172+
return self._request_json(
173+
"POST",
174+
"/v1/invites/create",
175+
json_body=payload,
176+
idempotency_key=idempotency_key,
177+
trace_id=trace_id,
178+
retryable=idempotency_key is not None,
179+
)
180+
181+
def get_invite(self, token: str, *, trace_id: str | None = None) -> dict[str, Any]:
182+
return self._request_json("GET", f"/v1/invites/{token}", trace_id=trace_id, retryable=True)
183+
184+
def accept_invite(
185+
self,
186+
token: str,
187+
payload: dict[str, Any],
188+
*,
189+
idempotency_key: str | None = None,
190+
trace_id: str | None = None,
191+
) -> dict[str, Any]:
192+
return self._request_json(
193+
"POST",
194+
f"/v1/invites/{token}/accept",
195+
json_body=payload,
196+
idempotency_key=idempotency_key,
197+
trace_id=trace_id,
198+
retryable=idempotency_key is not None,
199+
)
200+
165201
def upsert_webhook_subscription(
166202
self,
167203
payload: dict[str, Any],

tests/test_client.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,92 @@ def handler(request: httpx.Request) -> httpx.Response:
278278
assert client.get_capabilities()["ok"] is True
279279

280280

281+
def test_create_invite_success() -> None:
282+
token = "invite-token-0001"
283+
payload = {"owner_agent": "agent://owner", "recipient_hint": "receiver", "ttl_seconds": 3600}
284+
285+
def handler(request: httpx.Request) -> httpx.Response:
286+
assert request.method == "POST"
287+
assert request.url.path == "/v1/invites/create"
288+
assert request.headers["idempotency-key"] == "invite-create-1"
289+
body = json.loads(request.read().decode("utf-8"))
290+
assert body == payload
291+
return httpx.Response(
292+
200,
293+
json={
294+
"ok": True,
295+
"token": token,
296+
"invite_url": f"https://invite.example/{token}",
297+
"owner_agent": "agent://owner",
298+
"recipient_hint": "receiver",
299+
"status": "pending",
300+
"created_at": "2026-02-28T00:00:00Z",
301+
"expires_at": "2026-03-01T00:00:00Z",
302+
},
303+
)
304+
305+
client = _client(handler)
306+
assert client.create_invite(payload, idempotency_key="invite-create-1")["token"] == token
307+
308+
309+
def test_get_invite_success() -> None:
310+
token = "invite-token-0002"
311+
312+
def handler(request: httpx.Request) -> httpx.Response:
313+
assert request.method == "GET"
314+
assert request.url.path == f"/v1/invites/{token}"
315+
return httpx.Response(
316+
200,
317+
json={
318+
"ok": True,
319+
"token": token,
320+
"owner_agent": "agent://owner",
321+
"recipient_hint": "receiver",
322+
"status": "pending",
323+
"created_at": "2026-02-28T00:00:00Z",
324+
"expires_at": "2026-03-01T00:00:00Z",
325+
"accepted_at": None,
326+
"accepted_owner_agent": None,
327+
"nick": None,
328+
"public_address": None,
329+
},
330+
)
331+
332+
client = _client(handler)
333+
assert client.get_invite(token)["status"] == "pending"
334+
335+
336+
def test_accept_invite_success() -> None:
337+
token = "invite-token-0003"
338+
payload = {"nick": "@Invite.User", "display_name": "Invite User"}
339+
340+
def handler(request: httpx.Request) -> httpx.Response:
341+
assert request.method == "POST"
342+
assert request.url.path == f"/v1/invites/{token}/accept"
343+
assert request.headers["idempotency-key"] == "invite-accept-1"
344+
body = json.loads(request.read().decode("utf-8"))
345+
assert body == payload
346+
return httpx.Response(
347+
200,
348+
json={
349+
"ok": True,
350+
"token": token,
351+
"status": "accepted",
352+
"invite_owner_agent": "agent://owner",
353+
"user_id": "66666666-6666-4666-8666-666666666666",
354+
"owner_agent": "agent://accepted",
355+
"nick": "@Invite.User",
356+
"public_address": "invite.user@ax",
357+
"display_name": "Invite User",
358+
"accepted_at": "2026-02-28T00:00:10Z",
359+
"registry_bind_status": "propagated",
360+
},
361+
)
362+
363+
client = _client(handler)
364+
assert client.accept_invite(token, payload, idempotency_key="invite-accept-1")["status"] == "accepted"
365+
366+
281367
@pytest.mark.parametrize(
282368
("status_code", "expected_exception"),
283369
[

0 commit comments

Comments
 (0)