API
패널의 모든 기능은 서로 다른 두 개의 API로 사용할 수 있습니다. 둘 다 같은 계정, 같은 잔액, 같은 카탈로그를 사용하며 차이는 형식과 기능에 있습니다.
패널의 모든 기능은 서로 다른 두 개의 API로 사용할 수 있습니다. 둘 다 같은 계정, 같은 잔액, 같은 카탈로그를 사용하며 차이는 형식과 기능에 있습니다.
왜 API가 두 개인가요?
업계 전체가 쓰는 전통적인 리셀러 API(v2)는 단일 엔드포인트로 폼을 보내고 언제나 HTTP 200을 반환합니다. 기성 패널 소프트웨어가 기대하는 형식이 정확히 이것이므로 그대로 유지합니다. 반면 직접 시스템을 만드는 개발자는 이 형식의 한계에 계속 부딪혔습니다. 오류를 구분할 수 없었고, 카탈로그가 한 덩어리로 내려왔으며, 주문 상태를 알려면 끊임없이 조회해야 했습니다. v3는 이 필요를 위해 만들어졌습니다.
비교
| 항목 | 레거시 (v2) | 새 API (v3) |
|---|---|---|
| 형식 | 단일 엔드포인트, 폼 전송, action 매개변수 | 리소스 중심 REST, JSON 본문 |
| HTTP 상태 코드 | 실패해도 항상 200 | 실제 코드 (400, 401, 402, 404, 409, 429, 502) |
| 오류 | 자유 형식 텍스트 | type + 고정된 code + 현지화된 메시지 + param + doc_url |
| 주문 상태 | 현지화된 텍스트만 제공 | 고정된 기계 값과 별도의 표시용 라벨 |
| 서비스 설명 | 없음 | 10개 언어 설명, 평균 소요 시간, 플랫폼, 카테고리 |
| 주문 입력 항목 | 타입 이름으로 추측 | 서비스마다 자체 필드 스키마를 게시 |
| 가격 단위 | 표시되지 않음 (패키지에서 1000배 오차의 원인) | per_1000 또는 per_order로 명시 |
| 카탈로그 | 한 번의 응답에 전체 서비스 | 필터와 커서 페이지네이션 |
| 중복 주문 방지 | 없음 | Idempotency-Key |
| 상태 알림 | 지속적인 폴링 | 서명된 웹훅 또는 이벤트 스트림 |
| 스키마 | 없음 | OpenAPI 3.1 |
| 언어 | 영어와 터키어 (주소 분리) | 10개 언어 (헤더 또는 매개변수) |
어느 쪽을 골라야 하나요?
기성 패널 소프트웨어, 봇, 리셀러 패널을 쓰고 있다면 레거시 API를 고르십시오. 대부분은 API 주소와 키만 바꾸면 되고 몇 분 안에 동작합니다.
직접 애플리케이션, 스토어, 자동화를 만들고 있다면 v3를 고르십시오. 오류 처리, 중복 주문 방지, 알림 기반이 기본으로 제공되며 주문 폼을 서비스 스키마에서 그대로 생성할 수 있습니다.
시작하기
- 1키 탭에서 API 키를 하나 만듭니다.
- 2서비스 목록을 받아 사용할 서비스의 id와 필드 스키마를 확인합니다.
- 3주문을 먼저 preview로 검증한 다음 생성합니다.
- 4상태를 추적하려면 웹훅을 등록하거나 이벤트 스트림을 읽습니다.
직접 시스템을 만드는 개발자를 위해 설계한 REST API입니다. 리소스 중심 경로, 실제 HTTP 상태 코드, 기계가 읽을 수 있는 오류, 서명된 알림을 제공합니다.
기본 주소
모든 경로는 이 주소 뒤에 붙습니다. 버전은 경로에 있습니다. 호환성을 깨는 변경이 필요해지면 새 경로(v4)를 공개하고 지금 경로는 손대지 않은 채 그대로 동작합니다. 규약의 배포 날짜는 모든 응답의 X-Api-Version 헤더로 돌아옵니다.
https://panelfollows.com/api/v3인증
API 키는 Authorization 헤더에 Bearer 토큰으로 보냅니다. 대안으로 X-Api-Key 헤더도 허용합니다.
GET https://panelfollows.com/api/v3/account
Authorization: Bearer pf_live_...기존 레거시 키는 v3에서도 동작하므로 곧바로 시험해 볼 수 있습니다. 운영 환경에서는 v3 키를 쓰십시오. 라벨을 붙일 수 있고 하나씩 폐기할 수 있으며 평문으로 저장되지 않습니다.
빠른 시작
curl https://panelfollows.com/api/v3/services?limit=5 \
-H "Authorization: Bearer YOUR_API_KEY"언어
응답 언어는 Accept-Language 헤더 또는 ?lang= 매개변수로 정하며 매개변수가 헤더보다 우선합니다. 서비스 이름, 서비스 설명, 카테고리 이름, 주문 상태 라벨, 주문 필드 라벨, 오류 메시지가 모두 선택한 언어로 돌아옵니다.
기계 값은 어떤 언어에서도 바뀌지 않습니다. error.code, order.status, service.type, currency는 언제나 동일합니다. 분기 조건은 이 값으로 작성하고 사용자에게는 텍스트를 보여 주십시오.
Accept-Language: tr
# veya
GET https://panelfollows.com/api/v3/services?lang=tr요청과 응답 형식
요청 본문은 JSON(application/json)입니다. 빠른 테스트를 위해 form-urlencoded도 받습니다. 응답은 JSON이며 단일 리소스는 평범한 객체로, 목록은 data, has_more, next_cursor를 담은 봉투 형태로 돌아옵니다. 모든 객체에는 자신의 종류를 알려 주는 object 필드가 있습니다.
금액은 부동소수점이 아니라 십진 문자열("1.2340")로 돌아옵니다. 소수점 이하가 사라지지 않도록 클라이언트에서도 십진 타입으로 다루십시오. 통화는 USD입니다.
타임스탬프는 RFC 3339 형식입니다 (2026-08-21T00:24:45.255Z).
오류
실패하면 실제 HTTP 상태 코드가 돌아오고 본문에는 error 객체 하나가 담깁니다. 코드는 error.code 값을 기준으로 분기해야 합니다. 이 값은 고정되어 있고 언어에 따라 달라지지 않습니다.
HTTP/1.1 400 Bad Request
Content-Type: application/json
X-Request-Id: req_0c858d8af7f65eca001b2f5a
{
"object": "error",
"error": {
"type": "invalid_request_error",
"code": "quantity_out_of_range",
"message": "Miktar, bu servisin izin verdiği aralığın dışında.",
"param": "quantity",
"doc_url": "https://panelfollows.com/api-docs#error-quantity_out_of_range",
"request_id": "req_0c858d8af7f65eca001b2f5a"
}
}| type | 큰 분류입니다. 재시도할 만한 오류인지, 요청 쪽 잘못인지를 알려 줍니다. |
| code | 고정된 기계 값입니다. 분기 조건은 이 값으로 작성하십시오. |
| message | 선택한 언어로 된 사람이 읽는 설명입니다. |
| param | 오류를 일으킨 필드의 이름입니다. 해당하는 경우에만 옵니다. |
| doc_url | 이 문서에서 정확히 해당하는 절의 주소입니다. |
| request_id | 지원팀에 문의할 때 전달할 유일한 참조 값입니다. |
요청 제한
키당 분당 600건, 여기에 더해 IP당 분당 900건입니다. 모든 응답에 RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset 헤더가 함께 오므로 한도에 부딪히기 전에 스스로 속도를 늦출 수 있습니다. 한도를 넘기면 429와 Retry-After 헤더가 돌아옵니다.
페이지네이션
목록은 커서로 나눕니다. limit으로 페이지 크기(최대 500)를, starting_after로 직전 페이지 마지막 항목의 id를 보냅니다. has_more가 false가 될 때까지 계속하면 되고 next_cursor는 다음 요청에 쓸 커서를 바로 줍니다. 범위를 벗어난 limit은 조용히 잘리지 않고 오류를 반환합니다. 조용히 자르면 클라이언트가 "전부 받았다"고 착각하게 되기 때문입니다.
중복 주문 방지 (Idempotency-Key)
주문을 만들 때 요청에 임의의 Idempotency-Key 헤더를 넣으십시오. 연결이 끊겨 같은 키로 다시 시도해도 두 번째 주문은 생기지 않습니다. 첫 요청의 응답이 그대로 돌아오고 응답에 Idempotent-Replay: true 헤더가 붙습니다. 기록은 24시간 동안 보관합니다.
같은 키를 다른 본문과 함께 보내면 409 idempotency_key_reuse가 돌아옵니다. 이는 거의 언제나 클라이언트 쪽 키 생성 로직이 잘못되었다는 뜻입니다. 실패한 요청이 키를 소모하지는 않으므로 문제를 고친 뒤 같은 키로 다시 시도해도 됩니다.
주문 생성
curl -X POST https://panelfollows.com/api/v3/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f14e45f-ea3a-4b1c-9c1e-2b0d5c6a7e91" \
-d '{
"service": 1234,
"link": "https://instagram.com/username",
"quantity": 1000
}'서비스 필드 스키마
모든 서비스는 주문에 필요한 항목을 fields 배열로 게시합니다. 필드 이름, 타입, 필수 여부, 한계값, 그리고 사용자 언어로 된 라벨과 설명이 들어 있습니다. 어떤 필드의 determines_quantity가 true이면 수량은 그 필드에 입력된 줄 수에서 계산됩니다.
// Servisin kendi alan şemasından formu OTOMATİK üretmek:
// hiçbir servis tipini koda gömmeniz gerekmez.
const res = await fetch("https://panelfollows.com/api/v3/services/1234", {
headers: { Authorization: "Bearer YOUR_API_KEY", "Accept-Language": "tr" },
});
const service = await res.json();
for (const field of service.fields) {
renderInput({
name: field.name,
label: field.label, // kullanıcının dilinde
hint: field.description, // kullanıcının dilinde
required: field.required,
type: field.type, // url | integer | string | text_lines
min: field.min,
max: field.max,
// true ise miktarı bu alanın satır sayısı belirler
countsLines: field.determines_quantity === true,
});
}서비스 객체
{
"object": "service",
"id": 1234,
"name": "Instagram Takipçi | Türk | 30 gün telafi",
"description": "Gerçek hesaplardan Türk takipçi. Başlangıç 0-1 saat.",
"type": "default",
"platform": "instagram",
"category": { "slug": "instagram-takipci", "name": "Instagram Takipçi" },
"pricing": {
"rate": "1.2340",
"currency": "USD",
"unit": "per_1000",
"unit_note": "Fiyat 1000 adet içindir."
},
"limits": { "min": 100, "max": 100000 },
"features": { "refill": true, "cancel": false, "dripfeed": true },
"average_time_seconds": 4320,
"fields": [
{
"name": "link",
"type": "url",
"required": true,
"label": "Bağlantı",
"description": "Gönderimin yapılacağı profilin herkese açık adresi."
},
{
"name": "quantity",
"type": "integer",
"required": true,
"label": "Miktar",
"description": "Kaç adet gönderileceği.",
"min": 100,
"max": 100000
}
],
"is_active": true,
"updated_at": "2026-08-20T09:15:00.000Z"
}주문 미리보기 (드라이 런)
POST /orders/preview는 주문을 생성하지 않고 검증한 뒤 요금을 계산합니다. 고객에게 가격을 보여 주고 잔액이 충분한지 미리 확인할 수 있습니다. 잔액에서 차감되지 않으며 공급업체로도 전달되지 않습니다.
일괄 주문
POST /orders/batch로 한 번의 호출에 최대 50건의 주문을 보낼 수 있습니다. 항목은 순서대로 처리되고 각 항목이 자신의 결과를 받습니다. 하나가 실패해도 나머지는 생성되며 어떤 항목이 왜 실패했는지 그 항목의 결과에서 확인할 수 있습니다.
연관 객체 포함
주문 엔드포인트에 include=service를 보내면 서비스 객체가 응답 안에 함께 담기므로 두 번째 요청을 보낼 필요가 없습니다.
엔드포인트 레퍼런스
| 메서드 | 엔드포인트 | 설명 |
|---|---|---|
| GET | /api/v3 | Discovery document: version, endpoints, limits and event types. |
| GET | /api/v3/openapi.json | OpenAPI 3.1 schema for this API. |
| GET | /api/v3/account | Account balance, currency and current rate-limit window. |
| PATCH | /api/v3/account | Set the low balance alert threshold that triggers account.low_balance. |
| GET | /api/v3/services | List services with filters and cursor pagination. |
| GET | /api/v3/services/{id} | Retrieve one service, including its order field schema. |
| GET | /api/v3/categories | List categories with their active service counts. |
| GET | /api/v3/platforms | List platform keys usable as the ?platform= filter, with counts. |
| POST | /api/v3/orders | Create an order. Supports the Idempotency-Key header. |
| POST | /api/v3/orders/preview | Validate an order and compute its charge without creating it. |
| POST | /api/v3/orders/batch | Create up to 50 orders in one call; each item reports its own result. |
| GET | /api/v3/orders | List your orders, newest first. |
| GET | /api/v3/orders/{id} | Retrieve one order. |
| POST | /api/v3/orders/{id}/cancel | Request cancellation. Only for services whose features.cancel is true. |
| POST | /api/v3/orders/{id}/refill | Request a refill for a completed order. |
| GET | /api/v3/refills | List your refill requests, newest first. |
| GET | /api/v3/refills/{id} | Retrieve one refill; refreshes its status from the provider. |
| GET | /api/v3/events | Read your event stream oldest-first; the polling alternative to webhooks. |
| GET | /api/v3/webhooks | List your webhook endpoints. |
| POST | /api/v3/webhooks | Register a webhook endpoint. The signing secret is returned once. |
| GET | /api/v3/webhooks/{id} | Retrieve one webhook endpoint. |
| PATCH | /api/v3/webhooks/{id} | Update a webhook endpoint's url, events, description or active state. |
| DELETE | /api/v3/webhooks/{id} | Delete a webhook endpoint and its delivery log. |
| POST | /api/v3/webhooks/{id}/test | Send a test event to this endpoint, ignoring its event filter. |
| POST | /api/v3/webhooks/{id}/rotate_secret | Generate a new signing secret. The old one stops working immediately. |
| GET | /api/v3/webhooks/{id}/deliveries | Delivery log for one endpoint: attempts, response codes and errors. |
OpenAPI 스키마
모든 엔드포인트를 기계가 읽을 수 있는 형태로 정의한 문서입니다. 이 파일을 클라이언트 생성기(openapi-generator, Kiota)나 Postman에 넣으면 원하는 언어로 된 클라이언트를 바로 얻을 수 있습니다.
https://panelfollows.com/api/v3/openapi.json자주 묻는 질문
레거시 키가 v3에서도 동작하나요?
동작합니다. 시험해 보려고 새 키를 만들 필요는 없습니다. 다만 운영 환경에서는 v3 키로 옮기십시오. 폐기할 수 있고 평문으로 저장되지 않습니다.
가격은 왜 문자열인가요?
부동소수점 숫자는 소수 금액에서 값을 잃습니다. 금액을 문자열로 반환하고 클라이언트에서 십진 타입으로 다루면 반올림 차이가 생기는 문제 자체가 사라집니다.
unit 필드가 왜 중요한가요?
대부분의 서비스는 1000개 단위로 가격이 매겨지지만(per_1000) 패키지 서비스는 한 건 단위로 판매되며(per_order) 가격은 패키지 전체를 뜻합니다. 이 구분을 보지 않고 계산한 연동은 패키지 가격을 1000배 잘못 계산했습니다.
주문은 생성됐는데 상태가 pending에 머물러 있습니다. 무슨 일인가요?
공급업체로 전달이 지연됐을 수 있습니다. 잔액은 이미 잡혀 있고 주문은 사라지지 않습니다. 저희 팀이 자동으로 다시 보냅니다. 응답의 processing_delayed 필드가 이 상태를 나타냅니다.
취소와 리필은 모든 서비스에서 되나요?
아닙니다. 서비스 객체의 features.cancel과 features.refill을 확인하십시오. 지원하지 않는 서비스에서 해당 엔드포인트를 호출하면 400이 돌아옵니다.
두 API를 동시에 쓸 수 있나요?
쓸 수 있습니다. 같은 계정, 같은 잔액, 같은 주문입니다. v2로 만든 주문을 v3에서 조회할 수 있습니다.
업계 표준으로 자리 잡은 전통적인 리셀러 API입니다. 기성 패널 소프트웨어가 기대하는 형식이 바로 이것입니다.
엔드포인트
POST https://panelfollows.com/api/v2
POST https://panelfollows.com/api/v2/tr인증
모든 요청에 key 매개변수를 함께 보냅니다. 키는 비밀로 유지하고 유출됐다면 즉시 새로 발급하십시오.
요청과 응답 형식
요청은 POST로 폼(application/x-www-form-urlencoded)으로 보내고 응답은 JSON입니다. 실패한 경우에도 HTTP 200이 돌아오며 본문에 { "error": "..." }가 들어 있습니다.
지금까지 문서에 없었지만 함께 지원하는 방식이 있습니다. GET으로도 호출할 수 있고 본문을 application/json으로 보낼 수도 있습니다.
요청 제한
키당 분당 240건, 여기에 더해 IP당 분당 300건입니다. 이를 넘는 요청은 429로 거절됩니다.
작업과 매개변수
| action | 매개변수 | 설명 |
|---|---|---|
| services | key, action | 활성 서비스를 모두 나열합니다 (id, 이름, 카테고리, 가격, 최소/최대, 리필, 취소, 분할 발송). |
| add | key, action, service, link, quantity[, runs, interval, comments, username, posts, min, max] | 새 주문을 만듭니다. service는 카탈로그의 서비스 id입니다. 분할 발송에는 runs와 interval을 더하고, 특수 타입에는 해당 필드를 함께 보냅니다. |
| status | key, action, order | orders | 주문 상태입니다. 한 건은 order로, 여러 건은 쉼표로 구분한 orders로 조회합니다. |
| balance | key, action | 계정 잔액과 통화입니다. |
| refill | key, action, order | orders | 리필 요청을 만듭니다. 요청은 공급업체로 곧바로 전달됩니다. |
| refill_status | key, action, refill | refills | 리필 상태를 조회합니다. |
| cancel | key, action, orders | 주문을 취소합니다. 공급업체가 취소를 지원하는 서비스에서만 동작합니다. |
예시
curl -X POST https://panelfollows.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=add" \
-d "service=1234" \
-d "link=https://instagram.com/username" \
-d "quantity=1000"
# Yanıt: { "order": 23501 }터키어 응답
주소 끝에 /tr을 붙이면 서비스 이름, 카테고리, 주문 상태, 오류 메시지를 터키어로 받을 수 있습니다. 매개변수, 작업, 응답 구조는 완전히 같고 키도 두 주소에서 모두 동작합니다. 기술 필드(type, refill_status, currency)는 표준 호환을 위해 영어로 유지됩니다.
v3로 옮기기
옮기는 것은 선택 사항입니다. 옮기기로 했다면 매개변수 이름이 그대로이므로 비즈니스 로직 대부분은 유지됩니다. 달라지는 것은 전송 방식과 오류를 읽는 방법입니다.
- 1키를 본문의 key 필드에서 Authorization: Bearer 헤더로 옮깁니다.
- 2action=... 대신 리소스 경로를 호출합니다 (add 대신 POST /orders).
- 3오류 확인을 "error 필드가 있는가"가 아니라 HTTP 상태 코드와 error.code로 합니다.
- 4주문 상태를 표시용 텍스트가 아니라 status 기계 값과 비교합니다.
- 5주문을 만들 때 Idempotency-Key를 넣습니다.
- 6상태 폴링을 웹훅으로 바꿉니다.
키는 계정 전체에 대한 접근 권한을 줍니다. 공유하지 말고, 클라이언트 쪽 코드에 넣지 말고, 공개 저장소에 올리지 마십시오.
v3 키
키는 필요한 만큼 만들 수 있고 각각에 라벨을 붙여 하나씩 폐기할 수 있습니다. 서버에는 키의 암호학적 요약값만 저장됩니다.
무료 계정 만들기레거시 키
전통적인 리셀러 API(v2)가 쓰는 단일 키입니다. v3에서도 동작합니다. 새로 발급하면 이전 키는 즉시 무효가 됩니다.
보안
- 키는 환경 변수에 두고 소스 코드에 적지 마십시오.
- 브라우저에서 실행되는 코드에 키를 넣지 말고 요청을 자체 서버로 우회시키십시오.
- 시스템마다 별도의 키를 만들면 하나를 폐기해도 나머지가 영향을 받지 않습니다.
- 유출이 의심되면 새 키를 먼저 투입하고 그다음에 기존 키를 폐기하십시오.
주문 상태가 바뀌면 서명된 알림을 여러분의 서버로 보냅니다. 상태를 계속 조회할 필요가 없습니다.
웹훅을 쓰는 이유
폴링은 느리면서 동시에 낭비입니다. 수천 건의 주문을 1분마다 물어보면 요청 한도를 다 써 버리면서도 변화는 몇 분 늦게 알게 됩니다. 웹훅을 쓰면 변화가 일어난 순간 바로 전달됩니다.
설정
- 1공개된 https 주소를 준비합니다 (로컬 주소와 내부 네트워크 주소는 허용되지 않습니다).
- 2아래에서 주소를 추가하고 한 번만 표시되는 서명 키를 저장합니다.
- 3받는 쪽에서 서명을 검증하고 2xx로 응답합니다.
- 4테스트 전송 버튼으로 처음부터 끝까지 확인합니다.
저희가 보내는 요청
POST /hooks/pf HTTP/1.1
Content-Type: application/json
Webhook-Id: evt_7f910fba7cd042ef9d9069ba5c074fa0
Webhook-Timestamp: 1787261223
Webhook-Signature: t=1787261223,v1=9c1e2b0d5c6a7e91...
{
"object": "event",
"id": "evt_7f910fba7cd042ef9d9069ba5c074fa0",
"type": "order.completed",
"created_at": "2026-08-21T00:27:03.531Z",
"data": {
"previous_status": "in_progress",
"order": {
"object": "order",
"id": 23501,
"status": "completed",
"status_label": "Tamamlandı",
"service": 1234,
"quantity": 1000,
"start_count": 4210,
"remains": 0,
"charge": "1.2340",
"currency": "USD"
}
}
}서명 검증
모든 요청에는 Webhook-Signature 헤더가 붙습니다. t는 타임스탬프이고 v1은 서명입니다. 서명은 비밀 키로 계산한 "<타임스탬프>.<원본 본문>" 문자열의 HMAC-SHA256 값입니다.
- 1헤더에서 t와 v1 값을 분리합니다.
- 2재전송 공격을 막기 위해 t가 5분보다 오래되지 않았는지 확인합니다.
- 3"<t>.<원본 본문>" 문자열의 HMAC-SHA256 값을 비밀 키로 계산합니다.
- 4결과를 v1과 상수 시간으로 비교하고 일치하지 않으면 요청을 거절합니다.
검증 예시
import crypto from "node:crypto";
import express from "express";
const app = express();
// ÖNEMLİ: imza HAM gövde üzerinden hesaplanır. JSON'a çevirip yeniden
// dizeye dönüştürürseniz boşluklar değişir ve imza tutmaz.
app.post("/hooks/pf", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
const header = req.get("Webhook-Signature") ?? "";
const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header);
if (!m) return res.sendStatus(400);
const [, timestamp, signature] = m;
// Tekrar saldırısına karşı: 5 dakikadan eski damgayı reddet.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400);
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${raw}`, "utf8")
.digest("hex");
const ok =
expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(raw);
// 2xx dönmezseniz gönderim artan aralıklarla tekrar denenir.
res.sendStatus(200);
if (event.type === "order.completed") {
// ... siparişi kendi sisteminizde tamamlandı olarak işaretleyin
}
});재시도
첫 시도는 이벤트가 발생한 즉시 이루어집니다. 2xx가 아닌 응답이 오거나 연결에 실패하면 1분, 5분, 30분, 2시간, 6시간 뒤에 다시 보냅니다. 총 6번의 시도 후에도 실패하면 전송이 실패로 표시되고 전송 기록에 남습니다.
이벤트 종류
필요한 종류만 구독하거나 전부 받을 수 있습니다. 주문 상태가 바뀌면 이벤트는 정확히 하나만 생성되며 새 상태에 가장 잘 맞는 종류가 선택됩니다.
| order.created | 주문이 생성되었습니다. |
| order.processing | 공급업체가 주문 처리를 시작했습니다. |
| order.completed | 주문이 완료되었습니다. |
| order.partial | 주문이 일부만 전달되었고 나머지는 환불되었습니다. |
| order.canceled | 주문이 취소되었거나 환불되었습니다. |
| order.updated | 상태가 위 경우가 아닌 다른 방식으로 바뀌었습니다. |
| refill.created | 리필 요청이 생성되었습니다. |
| refill.updated | 리필 상태가 바뀌었습니다. |
| account.low_balance | 잔액이 PATCH /account로 설정한 기준값 아래로 떨어졌습니다. 기준값 아래의 모든 주문이 아니라 넘어가는 순간에 한 번 발생하며, 잔액이 다시 기준값을 넘으면 재설정됩니다. |
웹훅을 띄울 수 없다면
같은 이벤트를 GET /api/v3/events에서 커서로 읽을 수 있습니다. 로컬에서 개발할 때, 고정 IP가 없을 때, 방화벽 뒤에 있을 때 이 방법을 쓰십시오.
// Webhook kuramıyorsanız (yerelde geliştirme, sabit IP yok) aynı bilgiyi
// imleçle çekebilirsiniz. İmleci kendi tarafınızda saklayın.
let cursor = loadCursor(); // en son işlediğiniz olayın "cursor" değeri
const url = new URL("https://panelfollows.com/api/v3/events");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("starting_after", String(cursor));
const res = await fetch(url, { headers: { Authorization: "Bearer YOUR_API_KEY" } });
const { data, has_more } = await res.json();
for (const event of data) {
handle(event); // sizin işleyiciniz
cursor = event.cursor; // imleci ilerlet
}
saveCursor(cursor);오류 (48)
| 코드 | 상태 | 설명 |
|---|---|---|
| missing_api_key | 401 | API 키가 전송되지 않았습니다. 'Authorization: Bearer <key>' 헤더로 보내세요. |
| invalid_api_key | 401 | 제공한 API 키가 유효하지 않습니다. |
| revoked_api_key | 401 | 이 API 키는 취소되어 더 이상 사용할 수 없습니다. |
| account_banned | 403 | 이 계정은 차단되었습니다. |
| account_suspended | 403 | 이 계정은 일시 정지되었습니다. |
| insufficient_scope | 403 | 이 API 키에는 해당 엔드포인트에 대한 권한이 없습니다. |
| invalid_json | 400 | 요청 본문이 유효한 JSON이 아닙니다. |
| unsupported_content_type | 415 | 지원하지 않는 Content-Type입니다. application/json 또는 application/x-www-form-urlencoded를 사용하세요. |
| method_not_allowed | 405 | 이 엔드포인트에서는 해당 HTTP 메서드를 사용할 수 없습니다. |
| payload_too_large | 413 | 요청 본문이 너무 큽니다. |
| missing_parameter | 400 | 필수 매개변수가 누락되었습니다. |
| invalid_parameter | 400 | 매개변수 값이 유효하지 않습니다. |
| invalid_link | 400 | 링크가 없거나 유효한 http(s) URL이 아닙니다. |
| invalid_quantity | 400 | 수량이 유효한 양의 정수가 아닙니다. |
| quantity_out_of_range | 400 | 수량이 이 서비스에서 허용하는 범위를 벗어났습니다. |
| invalid_comments | 400 | 댓글 필드가 비어 있거나 줄 수가 너무 많습니다. |
| invalid_username | 400 | 이 서비스에서 사용할 수 없는 사용자 이름입니다. |
| invalid_subscription | 400 | 구독 매개변수가 유효하지 않습니다. |
| invalid_runs | 400 | 드립피드에 사용할 'runs' 값이 유효하지 않습니다. |
| invalid_interval | 400 | 드립피드에 사용할 'interval' 값이 유효하지 않습니다. |
| dripfeed_not_supported | 400 | 이 서비스는 드립피드를 지원하지 않습니다. |
| missing_required_field | 400 | 이 서비스 유형에 필요한 필드가 누락되었거나 유효하지 않습니다. |
| service_inactive | 400 | 이 서비스는 현재 주문할 수 없습니다. |
| invalid_cursor | 400 | 페이지네이션 커서가 유효하지 않습니다. |
| invalid_limit | 400 | 'limit' 매개변수가 허용 범위를 벗어났습니다. |
| invalid_webhook_url | 400 | 웹훅 URL은 공개된 https:// 주소여야 합니다. |
| invalid_events | 400 | 요청한 이벤트 유형 중 하나 이상을 알 수 없습니다. |
| batch_too_large | 400 | 한 번의 일괄 요청에 항목이 너무 많습니다. |
| cancel_not_supported | 400 | 이 서비스는 취소를 지원하지 않습니다. |
| refill_not_supported | 400 | 이 서비스는 리필을 제공하지 않습니다. |
| unknown_endpoint | 404 | 알 수 없는 엔드포인트입니다. 사용 가능한 경로는 API 레퍼런스를 확인하세요. |
| service_not_found | 404 | 해당 id의 서비스가 없습니다. |
| order_not_found | 404 | 계정에 해당 id의 주문이 없습니다. |
| refill_not_found | 404 | 계정에 해당 id의 리필이 없습니다. |
| webhook_not_found | 404 | 계정에 해당 id의 웹훅 엔드포인트가 없습니다. |
| order_not_cancelable | 409 | 현재 상태 때문에 이 주문은 더 이상 취소할 수 없습니다. |
| cancel_rejected | 409 | 공급자가 취소 요청을 거부했습니다. |
| order_not_completed | 409 | 리필은 완료된 주문에만 요청할 수 있습니다. |
| duplicate_link | 409 | 이 링크에 이미 진행 중인 주문이 있습니다. 완료될 때까지 기다리세요. |
| idempotency_key_reuse | 409 | 이 Idempotency-Key는 다른 요청 본문으로 이미 사용되었습니다. |
| idempotency_in_progress | 409 | 이 Idempotency-Key로 보낸 요청이 아직 처리 중입니다. 잠시 후 다시 시도하세요. |
| webhook_limit_reached | 409 | 웹훅 엔드포인트 최대 개수에 도달했습니다. |
| insufficient_balance | 402 | 이 주문을 처리할 잔액이 부족합니다. |
| rate_limit_exceeded | 429 | 요청 한도를 초과했습니다. Retry-After 응답 헤더를 확인하세요. |
| provider_error | 502 | 공급자가 오류를 반환했습니다. 다시 시도하세요. |
| refill_failed | 502 | 리필 요청이 공급자에게 거부되었습니다. |
| service_temporarily_unavailable | 503 | 이 서비스는 일시적으로 사용할 수 없습니다. 나중에 다시 시도하세요. |
| internal_error | 500 | 당사 측에서 예기치 않은 오류가 발생했습니다. |