귀하의 API가 400 Bad Request를 본문 {"error": "invalid input"}과 함께 반환합니다. 사람 개발자는 문서를 열어 페이로드를 확인하고 누락된 필드를 찾아 1분 만에 수정합니다. 에이전트는 같은 두 단어를 읽고 아무 조치도 취할 수 없어 할 수 있는 유일한 일은 같은 요청을 다시 보내는 것입니다. 그리고 또다시 보냅니다. 그러다 결국 포기하고 사용자에게 API가 작동하지 않는다고 알립니다.
오류 응답은 에이전트가 가장 의존하지만 팀에서 가장 마지막에 설계하는 API의 부분입니다. 좋은 오류는 호출자에게 무엇이 잘못되었는지, 재시도가 도움이 될 수 있는지, 무엇을 변경해야 하는지 알려줍니다. 에이전트는 이 세 가지 모두에 따라 조치할 수 있습니다. 모호한 오류는 복구 가능한 문제를 실패한 작업으로 만듭니다.
이 가이드는 관계의 API 측면을 위해 작성되었습니다. 에이전트 오류 복구에 대한 저희 게시물은 클라이언트가 재시도, 백오프 및 서킷 브레이커로 무엇을 해야 하는지 다룹니다. 이 가이드는 해당 클라이언트 로직이 제대로 작동하려면 API가 무엇을 반환해야 하는지 다룹니다.
Apidog가 여기서 중요한 이유는 오류 응답이 대부분의 API에서 가장 적게 테스트되는 부분이기 때문입니다. 성공 경로를 테스트하는 것과 동일한 위치에서 사양에 정의하고, 목(mock)하고, 단언(assert)할 수 있습니다.
오류가 답해야 할 세 가지 질문
에이전트가 받는 모든 오류 응답은 추측 없이 세 가지 질문에 답할 수 있도록 해야 합니다.
이것이 내 잘못입니까, 아니면 서버의 잘못입니까? 4xx는 요청이 잘못되었고 변경하지 않고 반복하면 다시 실패할 것임을 의미합니다. 5xx는 서버에서 뭔가 잘못되었고 같은 요청이 나중에 성공할 수 있음을 의미합니다. 이것들을 구별할 수 없는 에이전트는 유효성 검사 오류에 대해 영원히 재시도하거나 일시적인 오류에 대해 포기합니다.
재시도해야 하는가, 그리고 언제? 일부 4xx 오류는 재시도할 수 있지만 일부는 그렇지 않습니다. 429는 대기 후 재시도할 수 있습니다. 409는 상태를 다시 읽은 후 재시도할 수 있습니다. 422는 페이로드를 변경하지 않고는 재시도할 수 없습니다. 어떤 경우인지 명시적으로 알려주세요.
정확히 무엇을 변경해야 합니까? 이것은 대부분의 API가 생략하는 필드입니다. “유효성 검사 실패”는 쓸모가 없습니다. “country가 US일 때 필드 customer.postal_code는 필수입니다”는 에이전트가 다음 시도에 적용할 수 있는 수정 사항입니다.
이 세 가지를 모든 오류에 포함시키면 대부분의 에이전트 재시도 폭풍이 사라집니다.
구조화된 오류 형식 사용
새로운 형식을 만들지 마세요. RFC 9457, HTTP API용 문제 상세 정보가 형식을 정의하고 있으며 이는 잘 지원됩니다.
{
"type": "https://api.example.com/errors/validation-failed",
"title": "Validation failed",
"status": 422,
"detail": "The field 'customer.postal_code' is required when 'country' is 'US'.",
"instance": "/v1/orders",
"errors": [
{
"field": "customer.postal_code",
"code": "required_conditional",
"message": "Required when country is US. Provide a 5-digit or 9-digit US postal code.",
"example": "94107"
}
],
"retryable": false,
"next_action": "Add customer.postal_code to the request body and send again."
}
네 가지 부분이 에이전트에게 중요한 역할을 합니다.
detail은 실제 필드와 실제 규칙을 명시하는 완전한 문장입니다. 카테고리가 아닙니다. 이 요청에서 실패한 특정 사항을 나타냅니다.
errors 배열은 기계가 읽을 수 있는 형식으로, 문제당 하나의 항목이 있으며 에이전트가 보낸 페이로드에 다시 매핑할 수 있는 필드 경로를 포함합니다. 모든 실패를 한 번에 반환하세요. 하나씩 반환하면 한 번의 수정이 다섯 번의 왕복으로 변합니다.
retryable은 불리언(boolean) 값이며, 상태 코드에서 추론할 사항이 아닙니다. 이것이 에이전트에게 가장 도움이 되는 확장 기능이며, 하나의 필드만 필요합니다.
next_action은 일반적인 지시 텍스트입니다. 모델은 오류 코드에서 추론하는 것보다 응답 본문의 명시적인 지시를 더 안정적으로 따르며, 여기에 한 문장만 있어도 종종 실패한 작업을 완료된 작업으로 바꿀 수 있습니다.
Google의 API 오류 설계 가이드는 다른 방향에서 유사한 결론에 도달하며, 특히 오류 상세 정보는 산문보다는 구조화된 목록에 속해야 한다고 강조합니다.
언제 다시 시도할지 알려주세요
일시적인 오류의 경우, 언제 다시 시도할지 알려주세요. 30초 기다려야 한다는 것을 아는 에이전트는 30초를 기다립니다. 모르는 에이전트는 아무 시간이나 선택할 것이고, 그 시간은 대개 너무 짧습니다.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/rate-limited",
"title": "Rate limit exceeded",
"status": 429,
"detail": "You have used 1000 of 1000 requests in the current minute window.",
"retryable": true,
"retry_after_seconds": 30,
"next_action": "Wait 30 seconds before sending this request again. Do not retry sooner."
}
Retry-After 헤더는 초 단위의 지연 시간 또는 HTTP 날짜를 허용합니다. 초 단위는 클라이언트가 처리하기 더 쉽습니다. 표준 클라이언트에는 헤더로 보내고, 모델에는 본문에 반복하여 보냅니다. 중복은 저렴하며 두 소비자 모두 가장 잘 읽을 수 있는 것을 얻습니다. 속도 제한의 세부 사항은 저희 속도 제한 초과 가이드와 서버 측이라면 API 속도 제한 구현 방법에서 다룹니다.
동일한 패턴이 유지보수 중인 503 및 잠긴 리소스에 대한 409에도 적용됩니다. 대기가 올바른 응답인 모든 오류는 숫자를 포함해야 합니다.
내부 정보를 절대 유출하지 마세요, 아무것도 반환하지 마세요
두 가지 실패 모드가 정반대의 극단에 있으며, 둘 다 에이전트에 해롭습니다.
첫 번째는 스택 트레이스입니다. 내부 예외 텍스트를 반환하면 프레임워크 버전, 파일 경로, 때로는 쿼리 조각이 노출됩니다. 이는 에이전트 문제 이전에 보안 문제이며, 신뢰할 수 없는 입력에 대한 API 테스트 게시물의 우려 사항이 직접적으로 적용됩니다. 또한 모델이 처리할 수 없는 텍스트로 컨텍스트 창을 넘치게 합니다.
두 번째는 빈 오류입니다. 본문이 없는 500 또는 {\"error\": true}와 같은 형태입니다. 에이전트는 아무것도 배우지 못하며, 유일한 선택은 재시도하거나 종료하는 것뿐입니다.
중간 경로는 상관관계 ID를 가진 안정적인 공개 오류입니다.
{
"type": "https://api.example.com/errors/internal",
"title": "Internal error",
"status": 500,
"detail": "The order could not be created due to an internal error. No order was created.",
"retryable": true,
"retry_after_seconds": 5,
"request_id": "req_01J8ZK3M2Q",
"next_action": "Retry once after 5 seconds. If it fails again, stop and report request_id req_01J8ZK3M2Q."
}
“주문이 생성되지 않았습니다”라는 문장은 가장 가치 있는 부분입니다. 모호한 쓰기 작업에 직면한 에이전트는 재시도가 중복을 초래할 위험이 있는지 결정해야 하며, 대부분 잘못된 결정을 내립니다. 현재 상태가 어떤지 알려주세요. 약속할 수 없는 경우에는 작업을 멱등하게 만들고 그렇게 명시하세요. 이는 AI 에이전트를 위한 멱등 키에 대한 저희 게시물에 있는 패턴입니다.
request_id는 사람이 결국 기록을 읽을 때 로그에서 해당 스레드를 추적할 수 있게 해줍니다. 이 ID가 실제로 어떤 것으로 해석될 수 있도록 API 가시성 가이드의 관행과 함께 사용하세요.
오류는 사양에 포함되어야 합니다
오류 형식이 OpenAPI 문서에 없으면, 생성된 클라이언트, 목(mock), 에이전트 도구의 관점에서 볼 때 존재하지 않는 것입니다. 대부분의 사양은 200을 상세히 설명하고 다른 모든 것은 대충 넘어갑니다.
responses:
'201':
description: Order created
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'422':
description: >
Validation failed. Not retryable without changing the request body.
The errors array names each invalid field.
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Problem' }
'429':
description: >
Rate limited. Retryable. Wait for retry_after_seconds before sending again.
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Problem' }
이러한 설명은 단순한 장식이 아닙니다. OpenAPI 사양을 에이전트 도구로 전환하는 방법에 대한 저희 가이드처럼, 사양에서 에이전트 도구를 생성할 때 해당 텍스트는 모델이 실패 사례에 대해 읽는 내용이 됩니다. “재시도 가능, 먼저 기다리세요”라고 명시된 설명은 “너무 많은 요청”이라고만 된 설명보다 더 나은 동작을 유도합니다.
성공뿐만 아니라 오류도 테스트하세요
오류 경로는 테스트 커버리지가 무너지는 부분인데, 이는 오류를 발생시키는 데 노력이 필요하기 때문입니다. 목(mock)을 사용하면 이러한 노력을 줄일 수 있습니다.

API 프로젝트에서 각 오류 응답을 정의한 다음, 에이전트가 모든 경우를 필요에 따라 만날 수 있도록 목(mock)하세요. Apidog에서는 엔드포인트 정의에 실패 응답을 추가하고 목을 전환할 수 있어, 실제 시스템에 아무런 영향을 주지 않고 422, 429, 500 오류에 대해 에이전트를 반복적으로 실행할 수 있는 방법을 제공합니다. 프로덕션 대신 목 API에 대해 에이전트 실행하기에 대한 저희 게시물에서 더 넓은 습관을 다룹니다.
구축할 5가지 사례:
- 여러 잘못된 필드가 한 번에 발생하는 유효성 검사 실패. 모든 문제가 하나의 응답으로 돌아오고, 에이전트의 다음 시도가 하나가 아닌 모든 문제를 수정하는지 확인합니다.
- 대기가 있는 속도 제한. 에이전트가 무차별 공격 대신 최소한
retry_after_seconds만큼 기다리는지 확인합니다. - 쓰기 작업 중 서버 오류. 에이전트가 재시도할 때 자동으로 중복을 생성하지 않는지 확인합니다.
- 인증 실패. 잘못된 토큰은 아무리 기다려도 해결되지 않으므로, 에이전트가 재시도하는 대신 중단하는지 확인합니다. 에이전트를 위한 최소 권한 API 키에 대한 저희 게시물에서 자격 증명 측면을 다룹니다.
- 형식이 잘못된 오류 본문. 유효한 JSON이 아닌 것을 반환하고 에이전트가 우아하게 저하되는지 확인합니다. 업스트림 프록시는 결국 이런 일을 발생시킬 것입니다.
이 세트를 시나리오로 저장하여 CI에서 실행되도록 합니다. 오류 처리는 보통 누군가 직렬 변환기를 리팩토링할 때 조용히 퇴보하며, 성공 경로 테스트 스위트는 이를 알아차리지 못할 것입니다.
더 나은 오류의 가치
가치는 세 가지 지점에서 나타나며, 일단 살펴보면 측정하기 쉽습니다.
낭비되는 재시도 감소. {\"error\": \"invalid input\"}에 직면한 에이전트는 일반적으로 종료하기 전에 동일한 페이로드를 두세 번 재시도합니다. 각 시도는 모델 턴과 전체 대화 컨텍스트에 비용이 듭니다. 누락된 필드를 명시하는 응답은 일반적으로 한 번의 수정된 시도를 유도합니다. 이는 일상적인 유효성 검사 오류에서 네 번의 호출과 두 번의 호출 사이의 차이입니다.
에스컬레이션 감소. 복구할 수 없는 에이전트는 작업을 사람에게 넘깁니다. 피할 수 있는 모든 인계는 에이전트가 방지해야 했던 비싼 결과입니다. 수정 사항을 명시하는 오류는 자동화 내에서 실행을 유지합니다.
디버깅 시간 단축. 사람이 필요한 경우, request_id와 정확한 detail을 통해 로그를 뒤지는 작업이 한 번의 조회로 바뀝니다. 이는 실행이 중단되는 순간에 적용되는, 저희 API 가시성 가이드가 상관관계에 대해 주장하는 것과 같은 논리입니다.
놓치기 쉬운 네 번째 이점도 있습니다. 이러한 개선 사항은 사람 개발자에게도 도움이 됩니다. 어떤 필드가 잘못되었는지 오류 메시지가 너무 구체적이라고 불평하는 사람은 아무도 없습니다.
에스컬레이션도 고려하여 설계하세요
일부 오류는 에이전트가 실제로 복구할 수 없습니다. 누락된 범위, 폐쇄된 계정, 사람의 결정이 필요한 규칙 등이 그렇습니다. 이러한 경우 오류의 역할은 깨끗하게 인계하는 것입니다. 무슨 일이 일어났는지, 사람이 무엇을 해야 하는지 알려주고, 인계를 저렴하게 만드는 상관관계 ID를 포함합니다.
그 응답은 사람이 읽는 어딘가에 도착해야 합니다. 에이전트가 할당된 작업을 처리하는 코딩 런타임이라면, 일반적으로 주변 플랫폼이 그 도착 지점이 됩니다. Sharkly는 에이전트의 결과와 실행 추적을 Task에 보관하고, 응답 또는 검토가 필요한 항목을 받은 편지함으로 라우팅하여, 차단된 실행이 로그의 한 줄이 아닌 작업으로 보이도록 합니다. “잘못된 입력”이라는 메시지는 검토자에게 에이전트에게 제공한 것 이상을 주지 않으므로, 오류 텍스트가 인계를 유용하게 만듭니다.

에이전트가 산문을 분석하게 하지 마세요
마지막으로, 유기적으로 성장한 API에서 흔히 볼 수 있는 안티 패턴입니다. 상태 코드는 올바르지만 본문은 한 문장이고, 각기 다른 실패에 대해 다른 문구를 사용합니다.
{ "message": "Sorry, that didn't work. Please check your details and try again." }
에이전트는 추측을 통해서만 이에 응답할 수 있습니다. 더 나쁜 것은, 팀들이 종종 이를 200 상태와 함께 사용하므로 클라이언트 라이브러리는 실패를 감지조차 하지 못합니다.
두 가지 규칙이 이를 해결합니다. 모든 개별 실패에 안정적인 기계가 읽을 수 있는 코드를 부여하여 에이전트가 “not enough”라는 문구 대신 insufficient_funds로 분기할 수 있도록 하세요. 그리고 클라이언트 측 편의성 주장이 어떻든, 성공 상태 코드로 실패를 반환하지 마세요. 오류가 포함된 200은 모든 재시도 정책, 모든 대시보드, 그리고 소유하고 있는 모든 경고에 보이지 않습니다.
에이전트가 읽을 수 있는 오류를 위한 체크리스트
- 모든 오류는 전체 API에서 하나의 일관된 구조화된 형식을 사용합니다.
detail은 특정 필드 또는 조건을 명시하며, 카테고리를 명시하지 않습니다.- 유효성 검사 오류는 필드 경로와 함께 모든 문제를 한 번에 반환합니다.
- 모든 오류에
retryable불리언 값이 표시됩니다. - 재시도 가능한 오류는 헤더와 본문에 초 단위 대기 시간을 포함합니다.
- 쓰기 실패는 생성되거나 변경된 것이 있는지 여부를 명시합니다.
- 모든 오류는 로그에서 해결되는 상관관계 ID를 포함합니다.
- 스택 트레이스, 프레임워크 문자열, SQL을 포함하지 않습니다.
- 오류 응답은 에이전트가 읽을 수 있는 설명과 함께 사양에 문서화되어 있습니다.
- 각 오류에 대한 목(mock)이 존재하며, 저장된 테스트는 CI에서 이를 실행합니다.
오류는 인터페이스입니다. 실제로 존재하는 호출자를 위해 설계하세요. 그 호출자는 점점 더 응답 본문이 지시하는 것을 정확히 수행하는 모델이 될 것입니다. 에이전트가 실제로 오류를 만나기 전에 오류 형식을 정의하고 목(mock)하려면 Apidog를 다운로드하세요.
자주 묻는 질문
RFC 9457을 사용해야 합니까, 아니면 나만의 오류 형식을 사용해야 합니까? 프로덕션에 이미 일관된 형식이 없다면 RFC 9457을 사용하세요. 일관성이 표준화보다 중요합니다. 절반의 엔드포인트를 새로운 형식으로 전환하는 것은 모든 곳에서 하나의 형식을 유지하는 것보다 나쁩니다. 어떤 형식을 사용하든 retryable 및 next_action 확장을 추가하세요.
next_action 텍스트를 API 응답에 포함해도 안전합니까? 예, 서비스가 고정된 템플릿 세트에서 이를 생성하는 경우 안전합니다. 에이전트가 이를 지시로 읽고 프롬프트 주입 경로가 되므로, 사용자 제공 콘텐츠를 해당 필드에 절대 반영하지 마세요. 신뢰할 수 없는 입력에 대한 API 테스트에 대한 저희 게시물에서 이러한 위험을 다룹니다.
유효성 검사 오류는 400이어야 합니까, 422이어야 합니까? 요청이 깨진 JSON과 같이 형식이 잘못된 경우에는 400을 사용하고, 요청이 구문 분석되었지만 비즈니스 규칙에 실패한 경우에는 422를 사용하세요. 에이전트는 수정 사항이 다르기 때문에 분리된 상태에서 이점을 얻습니다. 이미 둘 다에 하나를 사용하고 있다면, 변경하기보다는 문서화하세요.
얼마나 많은 상세 정보가 지나친 것입니까? 호출자가 조치할 충분한 정보를 얻는 지점에서 멈추세요. 필드 이름, 규칙, 예시 값 정도면 보통 충분합니다. 내부 식별자, 쿼리 텍스트, 스택 프레임은 그 선을 넘어섭니다.
오류 메시지가 컨텍스트 창에 포함됩니까? 예, 그리고 재시도 전반에 걸쳐 반복되는 장황한 오류는 빠르게 누적됩니다. 몇 백 토큰 이내로 유지하세요. 에이전트용 API 응답 다듬기에 대한 저희 게시물은 성공뿐만 아니라 실패에도 적용됩니다.
재시도 불가능한 오류를 에이전트가 재시도하는 것을 어떻게 막을 수 있습니까? retryable: false로 설정하고, next_action에 명시하며, 도구 래퍼에서 이를 강제하여 모델의 판단이 유일한 보호 장치가 아니도록 하세요. 여기서는 이중 안전 장치를 마련하는 것이 올바른 방법입니다.
