
이제 AI 에이전트가 API에 비용을 지불할 수 있습니다: 두 가지 경로로 동일한 요청을 실행해 본 결과
요약
AI 에이전트가 API 호출 시 직접 비용을 지불할 수 있도록 설계된 PayKit의 작동 방식을 분석합니다. HTTP 402 응답을 결제 챌린지로 변환하여 에이전트가 스테이블코인으로 즉시 결제하고 요청을 재전송하는 두 가지 경로를 테스트했습니다.
핵심 포인트
- PayKit은 HTTP 402 응답을 결제 챌린지로 변환하여 에이전트의 결제를 유도함
- MPP(Multi-Party Payment)를 통해 플랫폼 수수료와 판매자 정산을 자동화 가능
- 스테이블코인을 활용한 소액 결제에 최적화되어 있으며 카드 네트워크와는 다름
- 에이전트가 머신 측 클라이언트를 통해 결제 서명 및 재전송 과정을 수행함
이제 AI 에이전트가 API에 비용을 지불할 수 있습니다. 나는 두 가지 경로(Rails)로 동일한 요청을 실행해 보았습니다.
짧은 결론
- PayKit은 HTTP
402 Payment Required를 에이전트가 서명하고 재전송(replay)할 수 있는 결제 챌린지(payment challenge)로 변환합니다. - 나는 동일한
GET /api/v1/fortune요청을 MPP charge와 x402 exact 경로 모두로 보냈습니다. 두 경로 모두 정상적인 200 응답으로 끝났습니다. - MPP는
/api/v1/joke에 대해 분할 결제(split)도 수행했습니다: $0.003은 플랫폼 계정으로, 나머지는 판매자에게 전달되었으며, 판매자 지급 라벨(payout label)이 유지된 영수증이 발행되었습니다. - API에 소액의 스테이블코인(stablecoin) 결제가 필요하고 결제자가 지갑을 제어할 수 있는 경우에 이를 사용하세요. 카드 네트워크처럼 취급해서는 안 됩니다. 차지백(chargeback, 결제 취소)은 없으며, 데모를 실행하기 전에 내가 겪었던 것처럼 패키지에 다듬어지지 않은 거친 부분이 있습니다.
첫 번째 응답은 402였습니다
나는 Solana Foundation의 PayKit 저장소(repository)를 클론하고, Playground API를 시작한 뒤, 일반적인 HTTP 클라이언트로 GET /api/v1/fortune을 호출했습니다. 서버는 402 Payment Required로 응답했습니다.
그것은 숨겨야 할 오류가 아니라 유용한 부분이었습니다. 응답에는 요청 비용이 10,000 USDC 기본 단위, 즉 $0.01라고 명시되어 있었으며, 동일한 accepts 배열 내에 두 가지 제안(offer)을 광고하고 있었습니다. 하나는 exact 스킴(scheme)을 사용하는 x402였고, 다른 하나는 charge 의도(intent)를 가진 MPP였습니다.
그 다음, 나는 PayKit 클라이언트에 일회용 Solana 키페어(keypair)를 부여하고 MPP를 선택하여 동일한 URL을 재전송했습니다. 클라이언트는 챌린지를 읽고, 결제에 서명하고, 증명(proof)을 보낸 뒤, 다음과 같은 JSON 응답을 받았습니다:
여기서 한 가지 정직하게 밝힐 세부 사항이 있습니다. 나는 LLM에 프롬프트를 주고 그것이 결제를 선택했다고 주장하지 않았습니다. 나는 AI 에이전트가 호출할 수 있는 머신 측 HTTP 클라이언트로 PayKit의 createPayKitClient를 실행했습니다. 402를 읽고, 요청된 결제에 서명하고, 동일한 URL을 재전송하는 것이 내가 테스트하고자 했던 에이전트 대상의 결제 단계였습니다.
{"fortune":"Your code will compile on the first try today."}
응답에는 payment-receipt와 x-payment-settlement-signature가 포함되어 있었습니다. 저는 해당 참조를 확인하기 위해 호스팅된 샌드박스 RPC(Remote Procedure Call)에 쿼리를 보냈습니다. 트랜잭션은 존재했으며, err 필드는 null이었습니다. 이것이 “HTTP 데모가 200을 반환했다”와 “결제 경로가 실제로 정산되었다”를 가르는 경계선입니다.
SDK가 라우트 핸들러(Route Handler)에서 제외하는 것
애플리케이션의 표면적은 작습니다. TypeScript 예제는 수락된 프로토콜(Protocols), 연산자(Operator), 가격표(Pricing table)를 createPayKit에 전달한 다음, Express 라우트에 pay.express('fortune')를 부착합니다.
결제되지 않은 요청은 402 에러를 받습니다. 결제 증빙(Payment proof)이 포함된 요청은 핸들러가 실행되기 전에 검증 및 정산됩니다. 핸들러는 pay.payment(req)를 통해 검증된 영수증을 읽을 수 있습니다. 모든 유료 라우트에 대해 MPP(Multi-Protocol Payment) 분기 옆에 x402 분기를 별도로 만들 필요가 없습니다.
리포지토리(Repository)의 인터페이스 사양은 이 경계를 명확하게 만듭니다. 애플리케이션은 가격이 책정된 게이트(Priced gate)를 선언합니다. 디스패처(Dispatcher)는 프로토콜 제안을 수집하고 자격 증명(Credential)을 감지합니다. 어댑터(Adapter)는 결제를 검증하고 정산합니다. 애플리케이션은 프로토콜 중립적인 Payment 객체를 보게 됩니다.
저는 이 주장을 단순한 슬로건으로 취급하지 않고 리포지토리를 통해 확인했습니다. docs/paykit-interface.md에는 세 가지 레이어와 애플리케이션 프리미티브(Primitives)인 require_payment, paid?, payment()가 명시되어 있습니다. TypeScript의 src/client/index.ts에는 402 프로브(Probe), 서명 경로(Signing path), 그리고 재시도(Retry) 로직이 포함되어 있습니다. Playground API의 index.ts는 가격표를 fortune, joke, summarize 라우트에 연결합니다.
이러한 분리는 에이전트(Agent)에게 매우 중요합니다. 에이전트는 가맹점의 개인적인 라우팅 테이블이 아니라, 결제 요구 사항(Payment challenge)을 이해해야 합니다. 서버는 비즈니스 핸들러를 변경하지 않고도 수락하는 결제 수단(Rail)을 변경할 수 있습니다.
하나의 요구 사항 뒤에 있는 두 가지 결제 수단 (Rails)
x402 exact
x402는 압축된 경로입니다. 서버는 수취인, 금액, 네트워크, 그리고 스킴(Scheme)을 보냅니다. 클라이언트는 USDC 전송에 서명하고, 원래의 HTTP 요청에 결제 증빙을 첨부하여 재시도합니다. 서버는 정산 내용을 검증하고 리소스를 반환합니다.
저는 x402를 선택하여 동일한 fortune 경로(route)를 호출했습니다. 클라이언트 진행 과정은 challenge, signing, paying, 그리고 paid 단계를 거쳤습니다. 서버는 샌드박스 트랜잭션 서명(transaction signature)이 포함된 x-payment-response를 반환했습니다. 저는 RPC를 통해 해당 서명을 조회했으며, 슬롯(slot) 436078484에서 트랜잭션 오류 없이 확인되었습니다.
이는 단일 가격과 단일 수취인을 가진 하나의 엔드포인트(endpoint)에 적합한 방식입니다. 그렇다고 해서 결제를 되돌릴 수 있게 만드는 것은 아닙니다. 제 실행 과정에서 클라이언트는 HTTP 요청 내에 머물며 결제 증빙에 서명했고, 카드 결제창을 열지 않고도 200 응답을 받았습니다. 이것이 카드 결제 상품이 제공하는 운영 기능을 대체하는 것은 아닙니다.
MPP 결제 (MPP charge)
MPP는 더 풍부한 결제 의도(payment intent)를 담고 있습니다. 그 차이점은 joke 경로에서 나타났습니다. 해당 경로의 가격은 $0.01이었으며, 결제 금액 중 $0.003은 플랫폼 수수료로 차감되었고 나머지는 판매자에게 지급되었습니다.
MPP 챌린지(challenge)에는 분할 수취인(split recipients) 및 메모(memo) 필드가 포함되어 있었습니다. 동일한 PayKit 클라이언트로 해당 경로를 호출한 후, 서버는 농담(joke) 내용과 판매자 지급 라벨(seller payout label)이 포함된 영수증을 반환했습니다. 샌드박스 RPC를 통해 정산 참조(settlement reference)를 다시 확인했습니다. err: null 상태로 존재했습니다.
이것은 단순히 x402의 확장 버전이 아닙니다. 마켓플레이스(marketplace)는 누가, 얼마를 받는지, 그리고 해당 지급액이 어떤 내부 판매에 속하는지를 명시해야 합니다. MPP는 애플리케이션 경로를 동일한 402 경계(boundary) 내에 유지하면서, 이러한 세부 정보를 챌린지와 영수증에 담아냅니다.
왜 하나의 경로가 MPP 전용이 되는가
이 예시에서 joke가 MPP 전용이 된 이유는 결제 형태(payment shape)가 그것을 요구하기 때문입니다. x402는 단일 수취인 전송에 집중합니다. 분할 결제는 PayKit가 두 프로토콜 모두 동일하게 표현할 수 있다고 가정하는 영역이 아니므로, 게이트(gate)가 MPP로 좁혀집니다.
이는 건강한 추상화(abstraction)입니다. accept: ['x402', 'mpp']가 모든 경로가 두 가지 방식(rails)을 모두 지원한다는 약속은 아닙니다. 고정 결제(fixed charge)는 두 방식 모두를 제공할 수 있습니다. 분할 결제(split charge)는 MPP를 제공할 수 있습니다. 사용량 게이트(usage gate)는 x402 한도를 승인하고 소비된 금액만 정산할 수 있습니다. 구독(subscription)이나 세션(session)은 완전히 다른 의도를 가집니다.
클라이언트는 챌린지(challenge) 중에서 호환 가능한 오퍼(offer)를 선택합니다. 가격, 수신자, 만료일 및 승인된 프로토콜에 대한 권한은 여전히 서버에 있습니다. 에이전트(agent)는 백지 수표를 협상하는 것이 아닙니다.
실제로 실행된 것과 문제가 발생한 부분
해당 리포지토리(repository)의 Playground API는 유용한 테스트 표면(test surface)입니다. 이는 호스팅된 Solana Payment Sandbox를 대상으로 고정 요금, x402 upto 사용량 과금, MPP 구독(subscription), 그리고 세션 스트림(session stream)을 노출합니다. 또한 API는 미결제 탐색(unpaid discovery) 및 문서화 기능도 제공합니다.
브라우저 Playground는 첫 번째 시도에서 깔끔하게 설치되지 않았습니다. 앱 패키지에서 x402-svm-2.16.0.tgz를 요청했기 때문입니다. 리포지토리의 벤더 레시피(vendor recipe)는 x402-svm-2.16.0-paykit.2.tgz를 생성했습니다. 저는 리포지토리를 수정하거나 종속성(dependency)을 몰래 교체하지 않았습니다. 격리된 체크아웃(checkout) 환경에서 외부 x402 서브모듈(submodule)을 빌드하고, MPP를 먼저 빌드한 후, API 패키지를 별도로 시작했습니다.
체크아웃 커밋은 358926f025459369f250091acfb5b911ccc1f1f3였으며, PayKit 패키지 버전은 0.7.0이었습니다. 저는 pnpm -C typescript/examples/playground-api start 명령어로 API를 시작했습니다. x402 참조값은 5JQov3btV1AreDmgrHmdRgtnGWQgr3KGakGNy4FQg5A5B35ddYqmn7NUQSrCEKBzSXQNVvZTMST1yStde5uhuvzF였습니다. MPP 참조값은 3Y7xANWu7hFZaBSuCtexxdouK9nVaew1PBk6RCtCdqdW4gaY71k5JMswc9G6wDc4Vdgc27TnmQpKhajNB69SwqJp 및 fo7TWRmBwEahZrKd25jyDfmRaQhf4ZDhBPFaZLJ6drbhnc5vVk3FrCx9GW5MuG2viAzPmoFXLFaDShUE1GX9h78였습니다. RPC 재읽기(rereads) 결과 슬롯(slot) 436078484, 436078475, 436078479가 반환되었으며, 세 가지 모두 err: null이었습니다.
그 후 API 측 실행을 통해 다음과 같은 영수증(receipts)을 받았습니다:
- x402 정액(exact) 및 MPP 과금 오퍼가 모두 포함된 미결제
fortune응답. - 판매자와 플랫폼 분할이 포함되어 있으나 x402 오퍼는 없는 미결제
joke응답. - $0.10의 x402
upto상한선이 설정된 미결제summarize응답. - MPP와 x402를 통해 동일한 fortune 요청을 완료한 PayKit 클라이언트.
- 샌드박스(sandbox)에 존재하며 재읽기 시
err: null을 반환한 MPP 및 x402 정산(settlement) 참조값.
README의 언어 테이블에는 TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin, Swift에 걸친 서버 또는 클라이언트 지원 여부가 나열되어 있습니다. 귀하의 서비스가 TypeScript로 작성되지 않았다면 이러한 폭넓은 지원은 유용합니다. 하지만 이것이 각 언어의 패키지를 설치하고 실제 릴리스 아티팩트(release artifact)를 확인하는 것을 대신할 수는 없습니다. 오늘 발생한 파일명 불일치는 사소한 문제이지만, 결제 라이브러리는 이러한 작은 틈새에서 실패하곤 합니다.
내가 측정하지 않은 네 가지 사항
이 샌드박스(sandbox) 실행에서는 결제자 측의 Solana 네트워크 수수료를 측정하지 않았습니다. 402 챌린지(challenge)에는 feePayer가 포함되었으며, API 실행 시에는 운영자 서명자(operator signer)를 수수료 지불자로 사용했습니다. 또한 MIT 라이선스 저장소에서 PayKit 사용 수수료를 발견하지 못했습니다. 이는 실제 운영(production) 비용 모델이 아닙니다. 체인 수수료, RPC 수수료 및 운영 비용은 의도된 네트워크 조건 하에서 여전히 실제 측정이 필요합니다.
결제가 정산(settle)된 후 애플리케이션이 500 에러를 반환하는 경우나, 재시도(retry)가 중복 결제 의도(payment intent)를 생성하는 케이스는 실행하지 않았습니다. PayKit은 402 결제 경계(boundary)를 처리합니다. 애플리케이션의 멱등성(idempotency), 환불 또는 재시도 정책을 결정하지는 않습니다. 실제 운영 서비스에는 자체적인 의도 ID(intent ID)와 중복 지출 방지 기능이 필요합니다.
직접적인 운영상의 답변은 불편하지만 명확합니다. 만약 정산이 성공한 후 핸들러(handler)가 500을 반환한다면, 이체는 이미 온체인(on chain)에 기록된 상태입니다. 이번 실행에서는 환불 또는 복구 경로를 테스트하지 않았으며, PayKit은 해당 이체를 자동으로 취소하지 않습니다. 애플리케이션은 반드시 의도 ID를 영속화(persist)해야 하며, 재시도 시 다시 서명하기 전에 해당 의도를 다시 읽도록 해야 하고, 서비스가 자체적인 환불 정책을 정의하도록 해야 합니다. 그렇지 않으면 재시도가 두 번째 결제로 이어질 수 있습니다.
내 실행에서 사용된 지갑은 일회용이었으며 100 sandbox USDC로 충전되었습니다. 실제 운영 환경에서는 여전히 키 관리(key custody), 호출당 및 일일 한도 설정, 잔액 부족 시 중단, 잘못된 수신자에 대한 에러 경로가 필요합니다. README에는 9개의 언어 인터페이스가 나열되어 있지만, 나는 TypeScript API와 클라이언트를 통해서만 오류 없는 결제 사이클을 실행했습니다. 파일명 불일치는 내가 왜 각 언어를 운영 준비가 되었다고 판단하기 전에 테스트해야 하는지를 보여주는 이유입니다.
직접적인 답변은 의도적으로 제한되어 있습니다. 저는 0.01달러의 API 비용 이외의 어떤 비용도 측정하지 않았으므로, 최종 마진을 주장하는 것이 아닙니다. 결제 후의 애플리케이션 실패나 재시도 시의 중복 의도(duplicate intent)를 테스트하지 않았으므로, PayKit이 환불이나 멱등성(idempotency)을 위한 정답이라고 말하는 것도 아닙니다. 키 보관(key custody)과 지출 한도(spending caps)를 결정해야 하는 주체는 독자가 아닌 서비스 운영자입니다. 여기서 제시된 증거는 단 하나의 주장만을 뒷받침합니다: 샌드박스(sandbox) 결제 왕복 과정이 성공적으로 작동했다는 것입니다.
에이전트가 이것을 사용해야 할까요?
네, 에이전트가 알려진 API 세트를 호출하고, 각 호출에 작은 비용이 발생하며, 지갑에 지출 한도가 설정되어 있는 경우라면 그렇습니다. 프로토콜은 거부된 요청을 기계가 읽을 수 있는 챌린지(challenge)로 변환합니다. 클라이언트는 요청된 결제에 서명하고 동일한 요청을 재시도합니다. 파싱해야 할 인보이스(invoice) 이메일도 없고, 클릭해야 할 브라우저 체크아웃(browser checkout)도 없습니다.
아니요, 제품이 사람들이 카드와 연관 짓는 보장(guarantees)을 필요로 한다면 아닙니다. 스테이블코인(Stablecoin) 결제는 최종적입니다. 분실된 키는 잊어버린 비밀번호가 아닙니다. 잘못된 수취인은 차지백(chargeback) 사례가 아닙니다. PayKit은 서버에 결제 경계(payment boundary)를 제공할 뿐, 완전한 자금 안전 정책을 제공하는 것은 아닙니다.
실질적인 순서는 간단합니다: 샌드박스에서 402 핸드셰이크(handshake)를 실행하고, 반환된 바디(body)를 검증하며, 체인 상의 결제를 다시 읽은 다음, 키를 어떻게 저장하고 지출을 어떻게 제한할지 결정하십시오. 이 네 단계를 증명하기 전에 프로덕션(production) 지갑에 자금을 충전하는 것은 앞뒤가 바뀐 일입니다.
유용한 결론
흥미로운 결과는 AI 에이전트가 곧 기업이 될 것이라는 점이 아니었습니다. 그보다 더 작고 테스트 가능한 것이었습니다. HTTP 클라이언트가 402를 확인하고, 스테이블코인 결제에 서명하고, 동일한 요청을 재현(replay)하여 200을 수신했습니다. 저는 두 가지 서로 다른 레일(rails)을 통해 이를 두 번 수행했습니다. 샌드박스는 두 결제 모두를 기록했습니다.
x402는 단일 수취인에 대한 더 깔끔한 청구 방식입니다. MPP는 결제에 분할(splits), 의도(intent), 영수증 메타데이터가 포함될 때 더 적합합니다. PayKit은 프로토콜의 차이가 중요한 부분에서 드러나도록 허용하면서도, 애플리케이션에 두 방식 모두를 위한 단일 게이트 표면(gate surface)을 제공합니다.
저는 이 결과를 신뢰합니다. 왜냐하면 이야기 속에 실패한 설치 사례도 그대로 포함했기 때문입니다. 결제 인프라(Payment infrastructure)는 잘 다듬어진 성공 경로가 당신을 오도할 수 있는 바로 그 지점입니다.
출처
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기