오류 응답 형식
Blue의 GraphQL API는 GraphQL 사양에 따라 표준화된 형식으로 오류를 반환합니다. 오류가 발생하면 응답에는 무엇이 잘못되었는지에 대한 자세한 정보가 포함된 errors 배열이 포함됩니다.
예시 오류 응답
{
"errors": [
{
"message": "Todo was not found.",
"extensions": {
"code": "TODO_NOT_FOUND"
}
}
]
}
오류 구조
각 오류 객체는 다음을 포함합니다:
- message: 오류에 대한 사람이 읽을 수 있는 설명
- extensions.code: 프로그래밍적 처리를 위한 기계 가독성 오류 코드
프로덕션 오류 안전성
Blue는 오류 노출에 대한 안전 시스템을 구현합니다:
- 안전한 오류: 클라이언트에게 실제 오류 코드와 메시지를 표시
- 비안전 오류: 민감한 세부정보를 숨기기 위해 일반
INTERNAL_SERVER_ERROR 반환
- 리소스 없음: 모든
*_NOT_FOUND 오류는 안전하다고 간주되며 항상 노출됨
오류 카테고리
Blue는 다음 카테고리로 구성된 108개의 사용자 정의 오류 코드를 정의합니다:
인증 및 권한 오류
| 오류 코드 |
메시지 |
설명 |
UNAUTHENTICATED |
"인증이 필요합니다." |
요청에 인증이 필요하지만 제공되지 않음 |
FORBIDDEN |
"권한이 없습니다." |
인증되었지만 필요한 권한이 없음 |
리소스 없음 오류 (총 52개)
핵심 리소스
| 오류 코드 |
메시지 |
설명 |
TODO_NOT_FOUND |
"Todo를 찾을 수 없습니다." |
레코드/todo가 존재하지 않거나 사용자가 접근할 수 없음 |
TODO_LIST_NOT_FOUND |
"Todo 목록을 찾을 수 없습니다." |
목록이 존재하지 않거나 사용자가 접근할 수 없음 |
PROJECT_NOT_FOUND |
"프로젝트를 찾을 수 없습니다." |
프로젝트가 존재하지 않거나 사용자가 접근할 수 없음 |
COMPANY_NOT_FOUND |
"회사를 찾을 수 없습니다." |
회사가 존재하지 않거나 사용자가 접근할 수 없음 |
USER_NOT_FOUND |
"사용자를 찾을 수 없습니다." |
사용자가 시스템에 존재하지 않음 |
사용자 정의 필드 및 양식
| 오류 코드 |
메시지 |
설명 |
CUSTOM_FIELD_NOT_FOUND |
"사용자 정의 필드를 찾을 수 없습니다." |
사용자 정의 필드가 존재하지 않음 |
CUSTOM_FIELD_OPTION_NOT_FOUND |
"사용자 정의 필드 옵션을 찾을 수 없습니다." |
선택 필드 옵션이 존재하지 않음 |
FORM_NOT_FOUND |
"양식을 찾을 수 없습니다." |
양식 템플릿이 존재하지 않음 |
FORM_FIELD_NOT_FOUND |
"양식 필드를 찾을 수 없습니다." |
양식 필드가 존재하지 않음 |
프로젝트 구성 요소
| 오류 코드 |
메시지 |
설명 |
TAG_NOT_FOUND |
"태그를 찾을 수 없습니다." |
프로젝트에 태그가 존재하지 않음 |
AUTOMATION_NOT_FOUND |
"자동화 규칙을 찾을 수 없습니다." |
자동화 규칙이 존재하지 않음 |
CHART_NOT_FOUND |
"차트를 찾을 수 없습니다." |
대시보드 차트가 존재하지 않음 |
WEBHOOK_NOT_FOUND |
"웹훅을 찾을 수 없습니다." |
웹훅 구성 요소가 존재하지 않음 |
TEMPLATE_NOT_FOUND |
"템플릿을 찾을 수 없습니다." |
프로젝트 템플릿이 존재하지 않음 |
댓글 및 활동
| 오류 코드 |
메시지 |
설명 |
COMMENT_NOT_FOUND |
"댓글을 찾을 수 없습니다." |
댓글이 존재하지 않음 |
ACTIVITY_NOT_FOUND |
"활동을 찾을 수 없습니다." |
활동 로그 항목이 존재하지 않음 |
REACTION_NOT_FOUND |
"반응을 찾을 수 없습니다." |
댓글 반응이 존재하지 않음 |
기타 리소스
| 오류 코드 |
메시지 |
설명 |
FILE_NOT_FOUND |
"파일을 찾을 수 없습니다." |
파일 첨부가 존재하지 않음 |
SUBSCRIPTION_NOT_FOUND |
"구독을 찾을 수 없습니다." |
청구 구독이 존재하지 않음 |
INVOICE_NOT_FOUND |
"청구서를 찾을 수 없습니다." |
청구서 레코드가 존재하지 않음 |
CHECKLIST_NOT_FOUND |
"체크리스트를 찾을 수 없습니다." |
체크리스트가 존재하지 않음 |
CHECKLIST_ITEM_NOT_FOUND |
"체크리스트 항목을 찾을 수 없습니다." |
체크리스트 항목이 존재하지 않음 |
PROJECT_ROLE_NOT_FOUND |
"프로젝트 역할을 찾을 수 없습니다." |
사용자 정의 프로젝트 역할이 존재하지 않음 |
PROJECT_ACCESS_NOT_FOUND |
"프로젝트 접근 권한을 찾을 수 없습니다." |
사용자 프로젝트 접근 권한이 존재하지 않음 |
NOTIFICATION_NOT_FOUND |
"알림을 찾을 수 없습니다." |
알림이 존재하지 않음 |
DASHBOARD_NOT_FOUND |
"대시보드를 찾을 수 없습니다." |
대시보드가 존재하지 않음 |
KEY_NOT_FOUND |
"키를 찾을 수 없습니다." |
설정 키가 존재하지 않음 |
검증 오류
| 오류 코드 |
메시지 |
설명 |
BAD_USER_INPUT |
"유효하지 않은 입력입니다." |
일반 입력 검증 실패 |
VALIDATION_ERROR |
"유효하지 않은 매개변수" |
요청 매개변수가 검증에 실패함 |
BAD_EMAIL |
"유효한 이메일을 입력해야 합니다." |
유효하지 않은 이메일 형식 |
INVALID_IDS |
"유효하지 않은 ID입니다." |
요청에 있는 하나 이상의 ID가 유효하지 않음 |
PHONE_INVALID |
"전화번호가 유효하지 않습니다." |
유효하지 않은 전화번호 형식 |
URL_INVALID |
"URL이 유효하지 않습니다." |
유효하지 않은 URL 형식 |
INVALID_RECURRING_DUE_DATE |
"반복 작업에 대한 유효하지 않은 기한" |
반복 작업 날짜 검증 실패 |
INVALID_COLOR |
"유효하지 않은 색상" |
색상 값이 예상 형식과 일치하지 않음 |
비즈니스 로직 오류
한계 및 쿼터
| 오류 코드 |
메시지 |
설명 |
COMPANY_LIMIT |
"계정의 회사 한도에 도달했습니다." |
최대 회사 수에 도달함 |
PROJECT_LIMIT |
"회사의 프로젝트 한도에 도달했습니다." |
최대 프로젝트 수에 도달함 |
USER_LIMIT |
"회사의 사용자 한도에 도달했습니다." |
최대 사용자 수에 도달함 |
PROJECT_TEMPLATE_LIMIT |
"회사의 템플릿 한도에 도달했습니다." |
최대 템플릿 수에 도달함 |
CUSTOM_FIELD_LIMIT |
"사용자 정의 필드 한도에 도달했습니다." |
최대 사용자 정의 필드 수에 도달함 |
TODO_LIST_LIMIT |
"프로젝트의 목록 한도에 도달했습니다." |
프로젝트당 최대 목록 수 |
TOO_MANY_TODOS |
"할 일이 너무 많습니다." |
레코드 한도를 초과함 |
TOO_MANY_OPTIONS |
"옵션이 너무 많습니다." |
선택 필드 옵션 한도를 초과함 |
MAX_FILE_SIZE |
"최대 파일 크기는 4.8GB입니다." |
파일 업로드 크기 한도를 초과함 |
리소스 충돌
| 오류 코드 |
메시지 |
설명 |
TAG_ALREADY_EXISTS |
"태그가 이미 존재합니다." |
프로젝트에 동일한 이름의 태그가 존재함 |
COMPANY_SLUG_ALREADY_EXISTS |
"회사가 이미 존재합니다." |
회사 슬러그/URL이 이미 사용 중임 |
USER_ALREADY_EXISTS |
"사용자가 이미 존재합니다." |
사용자 이메일이 이미 등록됨 |
ALREADY_INVITED |
"사용자가 이미 초대되었습니다." |
사용자가 이미 대기 중인 초대가 있음 |
USER_ALREADY_IN_PROJECT |
"사용자가 이미 이 프로젝트에 있습니다." |
사용자가 이미 프로젝트 접근 권한이 있음 |
FILE_TYPE_NOT_ALLOWED |
"허용되지 않는 파일 유형입니다." |
업로드된 파일 유형이 제한됨 |
권한 및 접근 오류
| 오류 코드 |
메시지 |
설명 |
UNABLE_TO_DELETE_ONLY_ADMIN |
"회사의 유일한 관리자를 삭제할 수 없습니다." |
마지막 관리자를 제거할 수 없음 |
UNABLE_TO_UPDATE_OWNER |
"소유자를 업데이트할 수 없습니다." |
소유자 권한을 수정할 수 없음 |
TODO_LIST_IS_HIDDEN |
"Todo 목록이 숨겨져 있습니다." |
사용자의 역할에서 목록이 숨겨짐 |
COMPANY_NOT_ACTIVE |
"회사가 활성화되어 있지 않습니다." |
회사 구독이 비활성화됨 |
PROJECT_NOT_ACTIVE |
"프로젝트가 활성화되어 있지 않습니다." |
프로젝트가 보관됨/비활성화됨 |
데이터 무결성 오류
| 오류 코드 |
메시지 |
설명 |
UNABLE_TO_DELETE_LIST_WITH_TODOS |
"할 일이 있는 목록을 삭제할 수 없습니다." |
삭제하려면 목록이 비어 있어야 함 |
UNABLE_TO_DELTE_FILE |
"파일을 삭제할 수 없습니다." |
파일 삭제 실패 (코드의 오타) |
UNABLE_TO_MOVE_TODO |
"할 일을 이동할 수 없습니다." |
목록 간에 레코드를 이동할 수 없음 |
DEPENDENCY_HAS_DEPENDENCY |
"의존성에 의존성이 있습니다." |
순환 의존성이 감지됨 |
TODO_DEPENDS_ON_ITSELF |
"Todo가 자신에 의존합니다." |
자기 참조 의존성 |
Stripe/결제 오류
| 오류 코드 |
메시지 |
설명 |
STRIPE_CREATING_CUSTOMER |
"Stripe에서 고객 생성 중 오류가 발생했습니다." |
Stripe 고객 생성 실패 |
STRIPE_ALREADY_SUBSCRIBED |
"이미 구독 중입니다." |
활성 구독이 존재함 |
STRIPE_MISSING_PAYMENT_METHOD |
"결제 방법이 누락되었습니다." |
파일에 결제 방법이 없음 |
STRIPE_CREATING_SUBSCRIPTION |
"Stripe에서 구독 생성 중 오류가 발생했습니다." |
구독 생성 실패 |
STRIPE_UPDATING_SUBSCRIPTION |
"Stripe에서 구독 업데이트 중 오류가 발생했습니다." |
구독 업데이트 실패 |
STRIPE_CHECKOUT_SESSION |
"Stripe에서 체크아웃 세션 생성 중 오류가 발생했습니다." |
체크아웃 세션 생성 실패 |
STRIPE_CUSTOMER_PORTAL |
"Stripe에서 고객 포털 생성 중 오류가 발생했습니다." |
포털 세션 생성 실패 |
STRIPE_TAX_ID |
"세금 ID를 저장할 수 없습니다." |
세금 ID 검증/저장 실패 |
PAYMENT_REQUIRED |
"결제가 필요합니다." |
기능에 활성 구독이 필요함 |
NO_PAYMENT_REQUIRED |
"결제가 필요하지 않습니다." |
작업에 결제가 필요하지 않음 |
인증 및 세션 오류
| 오류 코드 |
메시지 |
설명 |
INVALID_CREDENTIALS |
"유효하지 않은 자격 증명입니다." |
사용자 이름/비밀번호가 올바르지 않음 |
EXPIRED_RESET_TOKEN |
"재설정 토큰이 만료되었습니다." |
비밀번호 재설정 토큰이 만료됨 |
OAUTH_FAILED |
"OAuth 프로세스가 실패했습니다." |
OAuth 인증 실패 |
SAML_NOT_ENABLED |
"SSO(SAML)가 활성화되어 있지 않습니다." |
SAML SSO가 구성되지 않음 |
SSO_AUTO_PROVISION_DISABLED |
"자동 프로비저닝이 비활성화되어 있습니다." |
SSO 사용자를 자동 생성할 수 없음 |
속도 제한
속도 제한은 graphql-rate-limit 패키지에 의해 다양한 구성으로 처리됩니다:
| 제한 유형 |
창 |
최대 요청 |
적용 대상 |
| Standard |
60s |
500 |
Most queries/mutations |
| Sensitive |
300s |
10 |
Password reset, auth operations |
| Expensive |
60s |
20 |
Complex queries, bulk operations |
| Search |
60s |
60 |
Search operations |
| File Upload |
60s |
100 |
File upload operations |
속도 제한에 걸리면, 한도를 초과했다는 메시지가 포함된 표준 GraphQL 오류를 받게 됩니다.
시스템 및 내부 오류
| 오류 코드 |
메시지 |
설명 |
INTERNAL_SERVER_ERROR |
"내부 서버 오류." |
프로덕션에서 비안전 오류에 대한 일반 오류 |
UNKNOWN_ERROR |
"알 수 없는 오류." |
예기치 않은 오류 발생 |
RESOLVER_NOT_FOUND |
"해결자를 찾을 수 없습니다." |
GraphQL 해결자가 없음 |
FIELD_NOT_IN_SCHEMA |
"스키마에 필드가 없습니다." |
요청된 필드가 존재하지 않음 |
오류 처리 모범 사례
클라이언트 측 처리
try {
const result = await client.mutate({
mutation: CREATE_TODO,
variables: { input }
});
} catch (error) {
if (error.graphQLErrors?.length > 0) {
const errorCode = error.graphQLErrors[0].extensions?.code;
switch (errorCode) {
case 'UNAUTHENTICATED':
// Redirect to login
break;
case 'TODO_NOT_FOUND':
// Show "Record not found" message
break;
case 'VALIDATION_ERROR':
// Display validation errors
break;
default:
// Show generic error message
}
}
}
일반 오류 시나리오
- 인증 필요:
UNAUTHENTICATED - 사용자가 로그인해야 함
- 권한 거부:
FORBIDDEN - 사용자가 필요한 역할/권한이 없음
- 리소스 없음:
*_NOT_FOUND - 리소스가 존재하지 않거나 사용자가 접근할 수 없음
- 검증 실패:
VALIDATION_ERROR, BAD_USER_INPUT - 입력 매개변수 확인
- 속도 제한: 속도 제한 헤더를 확인하고 백오프 구현
- 결제가 필요함:
PAYMENT_REQUIRED - 기능에 구독이 필요함
관련 문서
- 인증 - API 요청 인증 방법
- 요청하기 - 요청 형식 및 헤더
- 속도 제한 - 자세한 속도 제한 정보