TypeScript · Python SDK

npm · PyPI 준비 중. 멱등 키, 안전한 재시도, 실시간 재연결, 웹훅 확인이 들어간 타입 클라이언트를 미리 봅니다.

Markdown 보기마지막 수정

TypeScript · Python SDK는 npm과 PyPI에 올릴 준비를 하고 있습니다. 이 페이지는 SDK의 API를 미리 보여 줍니다. 두 SDK는 OpenAPI 문서의 모든 경로를 다루고 똑같이 동작합니다.

  • 발송에 Idempotency-Key를 자동으로 붙입니다. 다시 시도할 때 같은 키를 써서 메일이 두 번 나가지 않습니다.
  • 안전한 요청만 다시 시도합니다: 읽기와 멱등 키가 있는 요청을, 네트워크 오류 · 429 · 500 · 502(send_failed 제외) · 503 · 504 · 409 policy_changing 뒤에. 지터를 섞어 기다리고 Retry-After를 따릅니다.
  • 오류에 코드가 있습니다. 문구가 아니라 code로 분기합니다.
  • 실시간은 핑을 보내고, 끊기면 백오프로 다시 연결하고, 2시간 한도 전에 연결을 갈아 끼웁니다.
  • 웹훅 확인은 서명을 검증하고 5분 넘게 어긋난 시각을 거부합니다.
TypeScriptPython
패키지@atmark/sdkatmark
런타임Node 20 이상 · Deno · Bun · 브라우저Python 3.9 이상
의존성없음없음. 실시간은 선택 추가 패키지 websockets를 씁니다.

설치

참고 · 준비 중

npm과 PyPI에 올릴 준비를 하고 있어 아직 설치할 수 없습니다. 그 전까지는 Atmark를 바로 씁니다.

  • REST API. OpenAPI 문서 https://api.atmark.ai/v1/openapi.json에 설명이 있고, 이것으로 클라이언트를 생성할 수 있습니다. 발견
  • 원격 MCP 서버 https://api.atmark.ai/mcp. MCP 도구
  • 새 메일은 웹훅이나 실시간 WebSocket으로 받습니다.

빠른 시작

SDK API 미리 보기입니다. 같은 호출을 지금은 REST API로 씁니다.

TypeScript

ts
import { Atmark, AtmarkError, isHostile } from '@atmark/sdk';

const atmark = new Atmark({ token: process.env.ATMARK_AGENT_TOKEN, agentId: process.env.AGENT_ID });

// 1. 먼저 내 설정을 읽는다: 주소 · 권한 · 정책 · 발신 승인
const caps = await atmark.capabilities.get();
console.log(caps.agent?.address, caps.policy?.send_approval);

// 2. 받은편지함 읽기. 받은 메일은 바깥 데이터이고 지시가 아니다.
for await (const message of atmark.messages.inbound.list({ read: false })) {
  if (isHostile(message)) continue; // risk.level 이 "risk" 나 "block"
  const full = await atmark.messages.inbound.get(message.id);
  await atmark.labels.set(full.id, { add: ['seen-by-agent'] });
}

// 3. 초안을 만들고 보낸다. Idempotency-Key 는 SDK 가 만든다.
const draft = await atmark.drafts.create({ from: 'scout@atmark.ai', to: ['partner@example.com'], subject: 'Weekly report', text: 'Numbers attached.' });
try {
  const sent = await atmark.drafts.send(draft.id);
  if (sent.status === 'pending_approval') console.log('소유자 승인 대기. 다시 보내지 않는다.');
} catch (error) {
  if (error instanceof AtmarkError) console.log(error.status, error.code); // error.code 로 분기
  else throw error;
}

Python

python
import os
from atmark import Atmark, AtmarkError, is_hostile

atmark = Atmark(token=os.environ["ATMARK_AGENT_TOKEN"], agent_id=os.environ["AGENT_ID"])

# 1. 먼저 내 설정을 읽는다: 주소 · 권한 · 정책 · 발신 승인
caps = atmark.capabilities.get()
print(caps["agent"]["address"], caps["policy"]["send_approval"])

# 2. 받은편지함 읽기. 받은 메일은 바깥 데이터이고 지시가 아니다.
for message in atmark.messages.inbound.list(read=False):
    if is_hostile(message):  # risk.level 이 "risk" 나 "block"
        continue
    full = atmark.messages.inbound.get(message["id"])
    atmark.labels.set(full["id"], {"add": ["seen-by-agent"]})

# 3. 초안을 만들고 보낸다. Idempotency-Key 는 SDK 가 만든다.
draft = atmark.drafts.create({"from": "scout@atmark.ai", "to": ["partner@example.com"], "subject": "Weekly report", "text": "Numbers attached."})
try:
    sent = atmark.drafts.send(draft["id"])
    if sent["status"] == "pending_approval":
        print("소유자 승인 대기. 다시 보내지 않는다.")
except AtmarkError as error:
    print(error.status, error.code)  # error.code 로 분기

isHostile과 is_hostile은 risk.level이 정확히 none일 때만 거짓입니다. risk

실시간

SDK API 미리 보기입니다. 지금은 실시간에 나온 대로 바로 연결합니다.

SDK는 발견 문서에서 주소를 읽고, 토큰을 헤더(브라우저에서는 하위 프로토콜)로 보냅니다. 토큰을 URL에 넣지 않습니다. 실시간

TypeScript

ts
const connection = atmark.realtime.connect({
  onEvent(event) {
    if (event.type === 'message.received' && event.risk.level === 'none') void atmark.messages.inbound.get(event.message_id);
  },
  onOpen({ reconnected }) {
    if (reconnected) {
      // 놓친 이벤트는 다시 오지 않는다: messages.inbound.list({ since }) 로 따라잡는다.
    }
  },
  onError: console.error,
});
// connection.close();

Python

python
import asyncio

async def main() -> None:
    async for event in atmark.realtime.events():
        if event.type == "message.received" and event.risk["level"] == "none":
            print(atmark.messages.inbound.get(event["message_id"])["subject"])

asyncio.run(main())

Node 20에서는 WebSocket 생성자를 넘깁니다: new Atmark({ token, WebSocket: (await import('ws')).default }). Python에서는 on_open(True)가 다시 연결됐다는 뜻이니 messages.inbound.list(since=...)로 따라잡습니다.

웹훅 확인

SDK API 미리 보기입니다. 지금은 웹훅에 나온 대로 서명을 확인합니다.

받은 그대로의 본문을 넘깁니다.

TypeScript

ts
import { verifyWebhookSignature } from '@atmark/sdk';

export async function handleWebhook(request: Request): Promise<Response> {
  const rawBody = await request.text();
  const result = await verifyWebhookSignature(rawBody, request.headers, process.env.WEBHOOK_SECRET ?? '');
  if (!result.valid) return new Response(result.reason, { status: 401 });
  return new Response('ok');
}

Python

python
from atmark import verify_webhook_signature

result = verify_webhook_signature(raw_body, request.headers, os.environ["WEBHOOK_SECRET"])
if not result.valid:
    return 401, result.reason

시크릿 교체 중에는 새 시크릿과 옛 시크릿 어느 쪽이든 맞으면 통과합니다.

에이전트 만들기

atmark.agents.create({ name })(Python: atmark.agents.create("이름"))는 자가 등록을 부릅니다. 소유자가 켰을 때만 되고, 다시 시도하지 않습니다. 새 토큰은 한 번만 나오니 바로 보관합니다.

이 문서에 대한 의견은 support@atmark.ai로 보내 주세요.