본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 17. 03:39

당신의 AI 에이전트가 아직 처리하지 못하는 HTTP 코드: 402

요약

Cloudflare가 도입한 'Pay-Per-Crawl' 모델과 HTTP 402 상태 코드를 활용한 새로운 크롤링 과금 체계를 설명합니다. AI 에이전트가 예기치 못한 비용 발생을 방지하기 위해 402 응답과 관련 헤더를 처리하는 로직이 필요함을 강조합니다.

핵심 포인트

  • Cloudflare가 HTTP 402 코드를 활용한 Pay-Per-Crawl 서비스 활성화
  • 402 응답 시 crawler-price 헤더를 통해 크롤링 비용 확인 가능
  • 비용 지불 동의 시 crawler-exact-price 헤더를 포함하여 재요청 필요
  • AI 에이전트 개발 시 예기치 못한 과금을 막기 위한 제어 로직 필수

당신의 fetch 에이전트는 요청에 대한 두 가지 결말을 알고 있습니다. 200: 파싱(parse)하십시오. 403: 물러나거나, 교체하거나, 건너뛰십시오. 그 분기(branch)가 수년 동안 게임의 전부였습니다.

이제 세 번째 결말이 등장했으며, 이는 당신의 코드가 그냥 통과해 버리는 것입니다. 헤더에 달러 금액이 포함된 402 Payment Required입니다. Cloudflare는 2025년 7월에 Pay-Per-Crawl을 위해 이를 활성화했습니다. 403은 재시도(retries)로 당신을 처벌했습니다. 이는 눈에 보이는 시간 낭비였습니다. 단순한 402 자체는 청구 금액이 아니라 견적(quote)입니다. 하지만 당신의 에이전트가 당연한 행동, 즉 재요청(re-request)을 하고 가격에 동의하는 순간, 그것은 인보이스(invoice)가 됩니다. 그리고 여기서 뼈아픈 부분이 있습니다. 기본적으로 당신의 HTTP 클라이언트에는 이를 제어할 브레이크가 없습니다.

요약 (TL;DR)

  • Cloudflare Pay-Per-Crawl은 크롤러(crawlers)에게 HTTP 402 Payment Requiredcrawler-price 헤더로 응답합니다. 결제하려면 크롤러는 crawler-exact-price를 사용하여 재요청을 해야 하며 200 응답을 기대합니다. (Cloudflare, 2025-07-01)
  • 모든 분기를 연습하기 위해 제가 임의로 만든 10개 호스트의 합성(synthetic) 피스처(fixture)에서, 402

HTTP 역사의 대부분 동안, 402는 자리 표시자(placeholder)였습니다. RFC 9110, §15.5.3에서는 다음과 같이 명시하고 있습니다: "402 (Payment Required) 상태 코드는 향후 사용을 위해 예약되었습니다." (RFC 9110). 그것이 섹션의 전부입니다. 수십 년 동안 비어 있던 상태 코드였던 셈입니다.

Pay-Per-Crawl(크롤링당 결제)은 제가 이 코드가 대규모 프로덕션 환경에 연결된 것을 처음 본 사례입니다. 흐름은 명확합니다. 크롤러가 페이지를 요청합니다. 오리진(origin)은 200이나 403 대신, crawler-price: USD XX.XX라는 헤더와 함께 402를 반환합니다. 크롤러가 콘텐츠를 원한다면 다시 요청을 보내는데, 이때 비용 지불에 동의한다는 의미로 crawler-exact-price를 포함하며, 그러면 오리진은 200을 응답합니다. 크롤러가 첫 번째 요청 시 crawler-max-price를 먼저 제시하는 선제적인 변형 방식도 존재합니다. 이 모든 내용은 Cloudflare의 자체 공지에 나와 있습니다.

Stack Overflow와 Cloudflare는 올해 초 Stack Overflow의 데이터를 대상으로 공개적인 pay-per-crawl 협약을 실행했으며, 이는 퍼블리셔(publisher) 측의 입장을 알고 싶다면 읽어볼 가치가 있습니다. (인용하지 않을 부분에 대해 솔직히 말씀드리자면: 떠도는 몇몇 애그리게이터(aggregator) 포스트들은 구체적인 "봇 트래픽 -32% / 수익 +27%"라는 파일럿 수치를 인용하고 있습니다. 저는 이를 확인하기 위해 공식 Stack Overflow 블로그에 들어갔으나 해당 내용은 없었습니다. 따라서 저는 이를 제외하겠습니다. 논거를 전개하는 데 이 수치들이 꼭 필요한 것은 아닙니다.)

여기에 반론의 여지가 있는 부분이자, 페처(fetcher)를 작성하는 모든 이들에게 이것이 중요한 이유가 있습니다. "robots.txt는 죽었다"라는 견해는 잘못된 계층(layer)을 겨냥하고 있습니다. 강제성(enforcement)이 사라진 것이 아닙니다. 그것은 서버가 당신이 읽어주기를 바라는 정중한 텍스트 파일에서, 실제 가격이 포함된 실제 응답이 발생하는 네트워크 엣지(network edge)로 이동했을 뿐입니다. 과거에 "내가 허용되었는가?"를 묻던 크롤러의 질문은 조용히 "얼마인가?"로 바뀌었습니다. 그리고 "얼마인가"는 파싱(parsing) 문제가 아니라 런타임 정책 결정(runtime policy decision) 문제입니다. 당신의 클라이언트 라이브러리는 정책 결정을 내리지 않습니다. 당신이 내립니다.

내가 이미 실행 중인 트리, 그리고 402가 연결되는 지점

제가 여기에 덧붙일 말이 있는 유일한 이유이기에, 실제 수치를 솔직하게 말씀드리겠습니다. 제가 공개한 Apify actors를 통해 지금까지 기록한 프로덕션 실행(production runs) 횟수는 총 2,190회입니다. 튜토리얼 데모가 아닌 실제 사이트를 대상으로 한 진짜 작업들입니다. Trustpilot 리뷰 스크래퍼(scraper) 하나만으로도 이 중 962회를 차지합니다. 이는 단순히 과시하기 위한 통계가 아니라, 이 의사결정 트리(branch tree)가 어디에서 기인했는지를 보여주는 근거입니다.

이 모든 실행은 HTTP 응답을 기준으로 한 의사결정 트리(decision tree) 내에서 이루어집니다.

  • 200 → 파싱(parse)한다.
  • 403 → 강력한 차단(hard block). 물러나거나(back off), 신원(identity)을 교체하거나, 건너뛰고 로그를 남긴다. 구시대적인 강제 집행 방식이다.
  • 429 → 속도 제한(rate limited). 지터(jitter)를 적용하여 물러나고, 나중에 재시도한다.

이 트리에는 입 밖으로 내어 말할 가치가 있는 속성이 하나 있습니다. 바로 모든 분기(branch)가 무료라는 점입니다. 물론 틀린 말입니다. 403 폭풍이 몰아치면 실제 시간(wall-clock time)과 소모된 프록시(proxies) 비용이 발생하니까요. 하지만 계정의 잔액을 차감하지는 않습니다. 429가 할 수 있는 최악의 상황은 당신을 기다리게 만드는 것뿐입니다.

402는 이 속성을 깨뜨립니다. 이는 동일한 트리에 새로 생긴 잎(leaf)이며, 구조적으로 403 바로 옆에 위치합니다. 둘 다 "문이 단순히 열려 있지 않다"는 점에서는 같습니다. 하지만 403이 _안 된다_라고 말한다면, 402는 _공짜는 아니다_라고 말합니다. 이 단 하나의 차이로 인해, 당신의 기본 HTTP 클라이언트가 결코 내리도록 설계되지 않았던 세 가지 결정이 강제됩니다.

  1. 이 호스트에 대해 무료이거나 더 저렴한 소스가 있는가? 키가 필요 없는 API, 사이트맵(sitemap), 공개된 덤프(public dump) 등이 있을 수 있습니다. 있다면 그쪽으로 경로를 지정하십시오. 비용은 $0입니다.
  2. 이 단일 페이지가 요구하는 가격만큼의 가치가 있는가? 페이지당 상한선(ceiling)을 설정해야 합니다. 그렇지 않으면 비싼 페이지 하나가 조용히 전체 실행 비용을 잡아먹을 수 있습니다.
  3. 여전히 지불할 여력이 있는가? 지출에 따라 차감되는 실행당 예산(per-run budget)이 필요합니다. 그래야 50번째 402가 5번째 실행에서 이미 약속해버린 돈을 써버리는 일을 방지할 수 있습니다.

이 세 가지 중 그 어느 것도 requestshttpx에 들어있지 않습니다. 이것들은 정책(policy)입니다. 그리고 402 상황에서, 정책은 게임의 전부입니다.

코드를 보기 전에 직관적으로 확인해 봅시다. 이것이 왜 이론적인 이야기가 아닌지 체감하시길 바랍니다. Trustpilot 스크레이퍼(scraper)는 962번 실행되었습니다. 만약 해당 타겟들이 페이지당 고작 0.001달러인 Pay-Per-Crawl(크롤링당 과금) 뒤에 있다고 가정해 보십시오. 실행당 수백 페이지라면, 이는 실제로 반복되는 비용 항목이 됩니다. 몇 푼 안 되는 돈이 쌓여 송장에 기재될 만한 금액이 되는 것입니다. 단순한 "결제하고 계속 진행"하는 에이전트라면 눈 하나 깜짝하지 않을 것입니다. 그저 돈을 쓸 뿐입니다.

핸들러 (The handler)

전체 코드입니다. 표준 라이브러리(Stdlib)만 사용하며, 네트워크 연결이 없고 결정론적(deterministic)입니다. 즉, 보이는 출력이 곧 얻게 될 출력입니다. "네트워크"는 고정된 환경(fixture)입니다. 10개의 호스트가 있으며, 각 호스트는 응답 방식, 402 반환 시 가격, 그리고 무료 API 존재 여부를 포함합니다.

코드 성숙도: 장난감/예시용(toy/illustrative). 이것은 와이어 프로토콜(wire protocol)이 아니라 결정 로직(decision logic)을 모델링한 것입니다. 무언가를 실제로 배포하기 전에, 코드 뒤에 나오는 "무엇이 가짜인가(what's faked)" 섹션을 반드시 읽으십시오.

#!/usr/bin/env python3
"""
자율 페치 에이전트(autonomous fetch agent)를 위한 HTTP 402 Payment Required 핸들러.
...

직접 실행해 보세요: python3 -I agent_402_handler.py. 플래그도, 의존성(deps)도 필요 없습니다.

출력 (The output)

이것은 의역하지 않고 그대로 복사한 실제 표준 출력(stdout)입니다:

per-page cap=$0.05  run budget=$0.10  pages=10

=== NAIVE agent (pays every 402, no cap/api/budget) ===
...

단순한(naive) 블록을 위에서 아래로 읽어보십시오. 이 에이전트는 모든 것에 비용을 지불합니다. 0.0008달러짜리 페이지, 그다음 0.25달러짜리 페이지, 그다음 0.50달러짜리 페이지를 망설임 없이 결제합니다. 왜냐하면 로직 내에 가격에 대해 거절하는 내용이 전혀 없기 때문입니다. 최종 합계: 0.10달러 예산에서 0.9658달러 지출 — 즉 예산의 약 10배를 초과했으며, 규율 있는 에이전트가 지출한 금액($0.0600)보다 $0.9058를 더 썼습니다. 이는 스크립트 마지막에 출력되는 16.1배의 비율입니다. (두 개의 기준점이 있고 하나는 혼동하기 쉬우므로 명확히 설명하겠습니다: 예산 대비 약 10배, 규율 있는 에이전트 대비 16배. 이 네 가지 수치는 모두 위 표준 출력에서 가져온 것입니다.) 이 에이전트는 10페이지 중 9페이지를 가져왔습니다 — 그리고 이것이 바로 함정입니다. 생산적으로 보이기 때문입니다. 진짜 피해는 청구서가 도착했을 때나 확인하게 되는 열(column)에 숨어 있습니다.

예산이 설정된 블록(budgeted block)은 동일한 10개의 호스트에 대해 서로 다른 호출을 수행합니다. 두 개의 저렴한 페이지는 무료 API를 제공했으므로, API를 사용하고 비용을 전혀 지불하지 않았습니다. 페이지당 $0.05 상한선(cap)보다 가격이 높은 세 개의 페이지는 즉시 거부되었습니다 — SKIP_TOO_EXPENSIVE가 발생하며 402를 반환했고, 돈은 쓰이지 않았습니다. 이 블록은 세 개의 페이지에 대해서만 비용을 지불했습니다. 총합: $0.0600, 예산 범위 내입니다.

제가 숨기지 않을 트레이드오프 (trade-off)

예산이 설정된 에이전트는 6개의 페이지를 가져왔습니다. 단순한(naive) 에이전트는 9개를 가져왔습니다. 3개가 적습니다. 이것은 반올림 오차가 아니라, 바로 그 대가(deal)입니다.

상한선(cap)이 있다는 것은 당신이 shop.example.net, paywall.example.co, store.example.dev와 같은 페이지들 — 즉, 돈을 지불하면 얻을 수 있었던 페이지들 — 을 포기해야 함을 의미합니다. 때로는 그중 하나가 가장 중요한 페이지일 수도 있습니다. 가격 상한선은 도달 범위(reach)를 희생함으로써 비용 제어권을 사는 것입니다. 당신은 그 손실을 결과 수치에서 즉각적으로 느낍니다. 반면 초과 지출은 청구서가 나올 때까지 느끼지 못합니다. 이러한 비대칭성(asymmetry)이야말로 청구서가 나온 후가 아니라, 실행 _전(before)_에 정책을 설정해야 하는 온전한 이유입니다.

따라서 402에 대한 올바른 프레임(frame)은 "지불하느냐 차단되느냐"가 아닙니다. 그것은 다음과 같습니다: 단일 페이지가 당신에게 얼마의 가치가 있는지, 그리고 전체 실행(run)이 당신에게 얼마의 가치가 있는지를 미리 결정하십시오. 그런 다음 에이전트가 모든 리프(leaf) 노드에 대해 냉정하게 그 두 가지를 집행하도록 하십시오.

무엇이 가짜이고, 실제 프로덕션(production)에서 필요한 것은 무엇인가

저는 여러분이 데모보다는 논리를 신뢰하기를 바랍니다. 그래서 여기 데모가 속이는 부분이 있습니다:

  • 이 피스처(fixture)는 실제 Cloudflare 엔드포인트가 아닙니다. 가격, has_api 플래그, 상태 값 등은 모든 분기(branch)를 테스트하기 위해 제가 임의로 만든 것입니다. 이는 예시일 뿐입니다. 실제 Pay-Per-Crawl 가격은 퍼블리셔별로 설정되며, Python 리스트가 아닌 실제 402 응답의 crawler-price 헤더에서 읽어옵니다.
  • 예산(budget)은 인메모리(in-memory) 방식입니다. 실행할 때마다 초기화됩니다. 프로덕션(production) 핸들러에는 실행 중 충돌(crash)이 발생해도 유지되는 지속 가능한(durable) 예산 카운터가 필요합니다. 예를 들어 Postgres의 행(row), Redis 키 등과 같은 것이어야 합니다. 그렇지 않으면 재시작 시 전체 예산이 다시 충전되어 이중 지불(double-spend)이 발생할 수 있습니다.
  • 실제 결제는 이루어지지 않습니다. crawler-exact-price 헤더가 전송되지 않고, 실제로 200이 반환되지 않으며, 실제 돈이 이동하지도 않습니다. 여기서 PAID_FETCH는 하나의 레이블(label)일 뿐입니다. 실제 핸들러는 crawler-price를 읽고, 결정한 뒤, 합의 헤더(agreement header)와 함께 재요청을 보내며, 실제로 청구된 금액과 예상 금액을 대조하여 정산(reconcile)합니다.
  • 동시성(Concurrency) 문제는 단순한 예산 확인 방식을 망가뜨릴 것입니다. 두 명의 워커(worker)가 동시에 budget_left를 읽으면 둘 다 여유가 있다고 판단할 수 있습니다. 실제 실행당 예산 관리에는 원자적 감소(atomic decrement)가 필요합니다.

따라서 이것을 즉시 적용 가능한 코드가 아닌, 정책의 _형태(shape)_로 취급하십시오. 핵심은 그 형태에 있습니다. 무료 소스 확인, 페이지당 한도, 실행당 예산, 그리고 로그에 기록된 건너뛰기(skip)입니다. 이것들을 여러분의 페치 루프(fetch loop)에 연결하면 실제 프로토콜 부분은 기계적인 작업이 됩니다.

제가 선을 긋는 지점 — 그리고 진심으로 확신하지 못하는 지점

제가 무엇을 배포할 것이고, 어디에서 멈출 것인지 말씀드리겠습니다.

페이지당 한도와 실행당 예산, 둘 다 엄격하고 지속 가능하게 적용하는 것: 네, 첫날부터 적용하겠습니다. 결제 전 무료 소스 폴백(fallback): 네, 목록 중 가장 저렴하게 얻을 수 있는 이득입니다. 이미 가치가 높다고 알고 있는 도메인에 대해 더 많은 비용을 지불하는 도메인별 가격 계층(per-domain price tiers)? 제 생각에는 그것이 맞습니다. 하지만 실제 Pay-Per-Crawl 가격을 대상으로 테스트해보지 않았기 때문에, 계층 경계(tier boundaries)는 추측에 의존하고 있습니다. 오차 범위가 ±매우 클 수 있다고 생각하십시오.

제가 계속해서 고민하고 있는 지점은 이것입니다: 에이전트가 자율적으로(autonomously) 결제하는 것을 허용해야 할까요? 코드가 완전히 검증하지 않은 헤더를 기반으로 돈을 움직이게 두는 것은, 1,000번 중 999번은 괜찮다가 1,000번째에 재앙이 되는 종류의 일입니다. 저의 직관은 새로운 도메인으로부터 첫 번째 402가 발생했을 때는 인간 참여형 게이트(human-in-the-loop gate)를 두고, 그 이후에는 도메인별 한도(per-domain ceiling) 내에서 자율적으로 작동하게 하는 것입니다. 하지만 저는 아직 이에 대해 실제 과다 지출 사고를 겪어보지 못했습니다. Pay-Per-Crawl은 새로운 방식이며, 제 의견 뒤에는 실제 운영 환경에서의 결제 실행 경험이 전혀 없다는 점을 분명히 하고 싶습니다. 2,190번의 실행은 저에게 분기 트리(branch tree)를 가르쳐 주었습니다. 하지만 잎(leaf)에 가격이 매겨졌을 때 어떤 기분인지는 가르쳐 주지 않았습니다.

따라서, 댓글 유도용이 아닌 진짜 질문을 드립니다: 여러분은 어디에서 선을 긋습니까? 페이지당 상한선(per-page cap), 실행당 예산(per-run budget), 아니면 도메인별 가격 계층(per-domain price tiers)인가요? 그리고 에이전트가 자율적으로 결제하도록 허용하시겠습니까, 아니면 첫 번째 402 발생 시 인간 참여형(human-in-the-loop) 방식은 타협할 수 없는 원칙인가요? 만약 이미 Pay-Per-Crawl을 적용하여 배포하셨다면, 무엇이 망가졌는지 특히 듣고 싶습니다.

저는 실제 운영 환경에서의 스크래핑과 2,190번의 실제 실행이 실제로 가르쳐 주는 것들 — 실패, 비용, 문서에서 생략된 분기 트리(branch trees)에 대해 글을 씁니다. 다음 수치들을 확인하려면 팔로우해 주시고, 여러분의 402 정책을 댓글로 남겨주세요. 모든 댓글을 읽습니다.

AI 공개: AI 글쓰기 보조 도구로 초안을 작성하였으며, 발행 전 사람이 편집하였습니다. 위의 Python 코드는 표준 라이브러리(stdlib)만을 사용하며 제 컴퓨터(python3 -I)에서 실행되었습니다. 출력 블록은 stdout에서 그대로 복사되었으며, assert 문은 결정론적으로 통과합니다. $0.9658 / $0.0600 / 16.1x 수치와 페이지 수는 해당 스크립트의 정확한 출력값이며, 2,190 / 962 실행 횟수는 저의 Apify 운영 기록에서 가져온 것입니다. 외부 주장은 기본 출처(primary sources)로 연결됩니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0