apick-api 3.4.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -10
- package/README.md +122 -80
- package/docs/guide.en.md +124 -75
- package/docs/guide.ko.md +124 -75
- package/package.json +1 -1
- package/src/index.cjs +201 -1
- package/src/index.d.ts +122 -0
- package/src/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 3.
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 3.6.0 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- Skills 메서드 8종을 추가했습니다: `searchSkills()`, `getSkill()`, `quoteSkill()`, `runSkill()`, `getSkillRun()`, `getSkillRunResult()`, `cancelSkillRun()`, `skillUsage()`. 요청·응답이 JSON 이며 응답은 봉투 없이 `data` 에 담깁니다.
|
|
6
|
+
- Add eight Skills methods to search, inspect, quote, run, read, fetch the result of, cancel and list Skill runs. Requests and responses are JSON; responses are returned as-is in `data`.
|
|
7
|
+
- `runSkill()` 은 `idempotencyKey` 가 필수이며 `Idempotency-Key` 헤더로 보냅니다. 결과가 약속한 형식으로 반환된 실행만 차감됩니다.
|
|
8
|
+
- 생성형 AI 를 쓰는 Skill 은 실행마다 실제 사용량만큼 금액이 달라집니다. `quoteSkill()` 이 예상 금액(`estimated_points`)과 최대 금액(`max_points`)을 돌려주며, `maxCostPoints` 에는 `max_points` 를 넘기세요.
|
|
9
|
+
- For Skills that use generative AI the charge varies per run with actual usage. `quoteSkill()` returns `estimated_points` and `max_points`; pass `max_points` as `maxCostPoints`.
|
|
10
|
+
- `runSkill()` requires `idempotencyKey`, sent as the `Idempotency-Key` header. Only runs that return a result in the promised format are charged.
|
|
11
|
+
- `ApickApiError` 에 선택적 `details` 를 추가했습니다. Skills 오류 코드는 `serviceCode` 로 전달됩니다. `SKILL_CATEGORIES` 상수와 Skills 타입(`SkillSummary`, `SkillDetail`, `SkillQuote`, `SkillRun` 등)을 공개했습니다.
|
|
12
|
+
- Add optional `details` to `ApickApiError`; Skills error codes are surfaced through `serviceCode`. Export `SKILL_CATEGORIES` and the Skills types.
|
|
13
|
+
|
|
14
|
+
## 3.5.0 — 2026-09-30
|
|
15
|
+
|
|
16
|
+
- 간편인증 조회 상품 2종을 추가했습니다. `requestCashReceiptDeduction()`/`getCashReceiptDeduction()`은 현금영수증 소득공제 내역을 `incomeYears`(1~3)로, `requestTaxReturnHistory()`/`getTaxReturnHistory()`는 국세 신고내역을 `years`(1~10)로 조회합니다.
|
|
17
|
+
- Add two simple-auth data products: cash receipt income deductions (`incomeYears` 1-3) and national tax return history (`years` 1-10), each with a `request*()`/`get*()` pair and result payload types.
|
|
18
|
+
- 유튜브 공개 영상 API 4종을 추가했습니다: `youtubeMetadata()`, `youtubeThumbnail()`(JPG), `youtubeSubtitleList()`, `youtubeSubtitle()`(VTT·SRT·TXT).
|
|
19
|
+
- Add four public YouTube video methods: metadata, thumbnail (JPG), subtitle language list, and subtitle download (VTT/SRT/TXT).
|
|
20
|
+
|
|
21
|
+
## 3.4.1 — 2026-09-28
|
|
22
|
+
|
|
23
|
+
- README와 한국어·영어 가이드의 간편인증 5종 예제에 모든 진행 상태와 `resultAvailable` 종료 기준을 적용했습니다. 전체·부분 성공, 인증 거부·만료·실패와 `RESULT_EXPIRED`를 구분합니다.
|
|
24
|
+
- Align all five simple-auth polling examples in the README and Korean/English guides with every progress state and the `resultAvailable` completion criterion. Distinguish full/partial success, rejected/expired authentication, failure, and `RESULT_EXPIRED`.
|
|
25
|
+
- 순차 폴링 간격을 5→10→20→30초로 늘린 뒤 30초를 유지하며, 인증·전체 대기시간 제한과 자동 재접수 방지를 안내합니다. 문서 코드를 직접 실행하는 회귀 테스트를 추가했습니다.
|
|
26
|
+
- Document sequential polling at 5→10→20→30 seconds, capped at 30 seconds, with authentication/overall waiting limits and no automatic resubmission. Add regression tests that execute the documentation examples.
|
|
27
|
+
|
|
28
|
+
## 3.4.0 — 2026-09-27
|
|
11
29
|
|
|
12
30
|
- 간편인증 기반 조회 상품 5종(재직·보험료 확인, 금융소득 조회, 국민연금 가입내역, 운전면허 조회, 국가 건강검진 결과)을 추가했습니다. 각 상품은 `request*()`로 본인 간편인증을 접수하고 `get*()`로 상태·결과를 폴링합니다.
|
|
13
31
|
- Add five simple-auth-based data lookup products (employment/insurance premium check, financial income, National Pension join history, driver's license, national health checkup). Each product accepts a `request*()` call for identity verification and polls status/result with `get*()`.
|
package/README.md
CHANGED
|
@@ -71,6 +71,10 @@ Leave the allowed-IP list blank for unrestricted access. To restrict access, reg
|
|
|
71
71
|
| `googleSearch(keyword, options)` | 웹 검색 / Web search | JSON |
|
|
72
72
|
| `googleImageSearch(keyword, options)` | 이미지 검색 / Image search | JSON |
|
|
73
73
|
| `screenshot(url)` | 웹페이지 화면캡처 / Web screenshot | Binary |
|
|
74
|
+
| `youtubeMetadata(url)` | 유튜브 영상 정보 / YouTube video metadata | JSON |
|
|
75
|
+
| `youtubeThumbnail(url)` | 유튜브 썸네일 / YouTube thumbnail | JPG |
|
|
76
|
+
| `youtubeSubtitleList(url)` | 유튜브 자막 언어 목록 / YouTube subtitle languages | JSON |
|
|
77
|
+
| `youtubeSubtitle(url, lang, options)` | 유튜브 자막 다운로드 / YouTube subtitles | VTT·SRT·TXT |
|
|
74
78
|
| `createTtsJob(text, options)` | 한국어 내레이션 작업 접수 / Create TTS job | JSON |
|
|
75
79
|
| `getTtsJob(jobId)` | TTS 작업 상태 / TTS job status | JSON |
|
|
76
80
|
| `cancelTtsJob(jobId)` | 대기·생성 중 TTS 작업 취소 / Cancel waiting or processing TTS job | JSON |
|
|
@@ -95,6 +99,42 @@ Leave the allowed-IP list blank for unrestricted access. To restrict access, reg
|
|
|
95
99
|
| `requestNpsJoinHistory(input)` / `getNpsJoinHistory(transactionId)` | 국민연금 가입내역조회 / National Pension join history | JSON |
|
|
96
100
|
| `requestDrivingLicense(input)` / `getDrivingLicense(transactionId)` | 운전면허 조회 / Driver's license check | JSON |
|
|
97
101
|
| `requestHealthCheckup(input)` / `getHealthCheckup(transactionId)` | 국가 건강검진 결과 조회 / National health checkup results | JSON |
|
|
102
|
+
| `requestCashReceiptDeduction(input)` / `getCashReceiptDeduction(transactionId)` | 현금영수증 소득공제 내역 / Cash receipt income deductions | JSON |
|
|
103
|
+
| `requestTaxReturnHistory(input)` / `getTaxReturnHistory(transactionId)` | 국세 신고내역 조회 / National tax return history | JSON |
|
|
104
|
+
| `searchSkills(options)` / `getSkill(skillId)` | Skill 검색·상세 / Search and inspect Skills | JSON |
|
|
105
|
+
| `quoteSkill(skillId, input, options)` | Skill 견적(무료) / Quote a run (free) | JSON |
|
|
106
|
+
| `runSkill(skillId, input, options)` | Skill 실행 / Run a Skill | JSON |
|
|
107
|
+
| `getSkillRun(runId)` / `getSkillRunResult(runId)` / `cancelSkillRun(runId)` | 실행 조회·결과·취소 / Read, fetch result, cancel | JSON |
|
|
108
|
+
| `skillUsage(options)` | 내 Skill 실행 내역 / My Skill runs | JSON |
|
|
109
|
+
|
|
110
|
+
## Skills
|
|
111
|
+
|
|
112
|
+
검수를 거친 Skill 을 검색하고 실행합니다. 결과가 약속한 형식으로 반환된 실행만 포인트가 차감되고, 실패·시간초과·취소는 차감되지 않습니다. 생성형 AI 를 쓰는 Skill 은 실행마다 실제 사용량만큼 금액이 달라지므로, 실행 전에 `quoteSkill()` 로 예상 금액(`estimated_points`)과 최대 금액(`max_points`)을 확인하세요. 1회 이상 결제한 계정에서 실행할 수 있습니다.
|
|
113
|
+
Search and run reviewed Skills. Points are charged only when a result in the promised format is returned; failures, timeouts and cancellations are not charged. For Skills that use generative AI the amount varies per run with actual usage, so call `quoteSkill()` first to get the estimated (`estimated_points`) and maximum (`max_points`) amount. Running requires an account with at least one payment.
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import { randomUUID } from 'node:crypto';
|
|
117
|
+
|
|
118
|
+
const found = await apick.searchSkills({ query: '상품명', limit: 5 });
|
|
119
|
+
const skillId = found.data.items[0].skill_id;
|
|
120
|
+
const detail = await apick.getSkill(skillId); // input_schema, output_schema, price_points, estimated_points
|
|
121
|
+
const input = { product_name: '튼튼한 접이식 우산' };
|
|
122
|
+
const quote = (await apick.quoteSkill(skillId, input)).data; // 무료 / free: estimated_points, max_points
|
|
123
|
+
|
|
124
|
+
const idempotencyKey = randomUUID(); // 재시도할 때 같은 값을 다시 씁니다 / reuse on retry
|
|
125
|
+
let run = (await apick.runSkill(skillId, input, { idempotencyKey, quoteId: quote.quote_id, maxCostPoints: quote.max_points })).data;
|
|
126
|
+
while (!['succeeded', 'failed', 'timed_out', 'cancelled'].includes(run.status)) {
|
|
127
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
128
|
+
run = (await apick.getSkillRun(run.run_id)).data;
|
|
129
|
+
}
|
|
130
|
+
console.log(run.status, run.billing, run.result);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Skills 응답은 다른 API 와 달리 봉투 없이 그대로 `data` 에 담깁니다. 과금 상태는 `data.billing`(`reserved`·`captured`·`released`…)에서 확인하며 `meta.cost` 는 채워지지 않습니다. 응답을 받지 못했을 때는 **같은 `idempotencyKey`** 로 다시 호출하세요. 포인트는 한 번만 차감됩니다. 같은 키에 다른 입력을 보내면 `IDEMPOTENCY_CONFLICT` 로 거부됩니다.
|
|
134
|
+
Skills responses are returned as-is in `data`. Read the charge from `data.billing`; `meta.cost` is not populated. If a response is lost, call again with the **same `idempotencyKey`** — points are charged once. The same key with a different input is rejected with `IDEMPOTENCY_CONFLICT`.
|
|
135
|
+
|
|
136
|
+
접수 전에 거절된 요청은 `ApickApiError` 로 던져지며 `serviceCode` 에 `INVALID_INPUT`·`INSUFFICIENT_POINTS`·`PRICE_EXCEEDS_LIMIT`·`RATE_LIMITED` 같은 코드가, `details` 에 부가 정보가 담깁니다. 접수된 뒤 실패한 실행은 예외가 아니라 `status` 와 `failure_code` 로 확인합니다.
|
|
137
|
+
Requests rejected before acceptance throw `ApickApiError` with the code in `serviceCode` and extra information in `details`. A run that fails after acceptance is reported through `status` and `failure_code`, not an exception.
|
|
98
138
|
|
|
99
139
|
## JSON 결과 / JSON results
|
|
100
140
|
|
|
@@ -237,86 +277,86 @@ The four document-specific methods return JSON. `maskResidentNumber` returns PNG
|
|
|
237
277
|
|
|
238
278
|
## 간편인증 기반 데이터 조회 / Simple-auth data lookups
|
|
239
279
|
|
|
240
|
-
본인 간편인증이 필요한 조회 상품(
|
|
241
|
-
|
|
242
|
-
Products that require the user's own simple-auth verification (employment, income, pension, driver's license, health checkup) split the call into a `request*()` acceptance and a `get*()` poll. Use the `transactionId` from the accepted response to poll for the result.
|
|
243
|
-
|
|
244
|
-
아래 함수는
|
|
245
|
-
|
|
246
|
-
Use this helper for all
|
|
247
|
-
|
|
248
|
-
<!-- simple-auth-polling:start -->
|
|
249
|
-
```js
|
|
250
|
-
async function pollDataResult(getResult, accepted, { timeoutMs = 600_000 } = {}) {
|
|
251
|
-
const pending = new Set([
|
|
252
|
-
'AUTH_REQUESTED', 'AUTH_WAITING', 'AUTH_COMPLETED', 'COLLECTING', 'COLLECTED'
|
|
253
|
-
]);
|
|
254
|
-
const failed = new Set(['AUTH_REJECTED', 'AUTH_EXPIRED', 'FAILED']);
|
|
255
|
-
const completed = new Set(['SUCCESS', 'PARTIAL_SUCCESS']);
|
|
256
|
-
const delays = [5_000, 10_000, 20_000, 30_000];
|
|
257
|
-
const deadline = Date.now() + timeoutMs;
|
|
258
|
-
const transactionId = accepted.data.transactionId;
|
|
259
|
-
const stop = code => { throw Object.assign(new Error(code), { code }); };
|
|
260
|
-
let response = accepted;
|
|
261
|
-
let attempt = 0;
|
|
262
|
-
|
|
263
|
-
for (;;) {
|
|
264
|
-
const data = response.data;
|
|
265
|
-
if (data.errorCode === 'RESULT_EXPIRED') stop('RESULT_EXPIRED');
|
|
266
|
-
if (failed.has(data.status)) stop(data.errorCode || data.status);
|
|
267
|
-
if (data.resultAvailable === true) {
|
|
268
|
-
if (data.result == null) stop('INVALID_RESULT');
|
|
269
|
-
return response;
|
|
270
|
-
}
|
|
271
|
-
if (completed.has(data.status)) stop('RESULT_NOT_AVAILABLE');
|
|
272
|
-
if (!pending.has(data.status)) stop('UNKNOWN_STATUS');
|
|
273
|
-
|
|
274
|
-
const awaitingApproval = ['AUTH_REQUESTED', 'AUTH_WAITING'].includes(data.status);
|
|
275
|
-
const authDeadline = Date.parse(data.expiresAt || accepted.data.expiresAt);
|
|
276
|
-
const limit = awaitingApproval && Number.isFinite(authDeadline)
|
|
277
|
-
? Math.min(deadline, authDeadline) : deadline;
|
|
278
|
-
const timeoutCode = awaitingApproval && limit === authDeadline
|
|
279
|
-
? 'AUTH_WAIT_TIMEOUT' : 'CLIENT_POLL_TIMEOUT';
|
|
280
|
-
const remaining = limit - Date.now();
|
|
281
|
-
if (remaining <= 0) stop(timeoutCode);
|
|
282
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
283
|
-
Math.min(delays[Math.min(attempt++, delays.length - 1)], remaining)));
|
|
284
|
-
if (Date.now() >= limit) stop(timeoutCode);
|
|
285
|
-
response = await getResult(transactionId);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
<!-- simple-auth-polling:end -->
|
|
290
|
-
|
|
291
|
-
`AUTH_REQUESTED`·`AUTH_WAITING`은 사용자 승인을 기다리고, `AUTH_COMPLETED`·`COLLECTING`·`COLLECTED`는 결과가 준비될 때까지 계속 조회합니다. 과금 여부(`charged`, `success`)는 완료 기준이 아닙니다. 인증 대기에만 `expiresAt`을 적용하고 승인 후에는 전체 대기 한도를 적용합니다. 예제의 10분 한도는 클라이언트 정책이며, 진행 중인 호출의 제한 시간은 SDK의 `timeoutMs`로 별도 설정하세요. `AUTH_WAIT_TIMEOUT`·`CLIENT_POLL_TIMEOUT`·`RESULT_NOT_AVAILABLE`·`INVALID_RESULT`·`UNKNOWN_STATUS`는 예제에서 만드는 로컬 오류로, 응답 모순이나 알 수 없는 상태에서 무한 반복하지 않습니다. 통신 오류도 재시도 없이 호출자에게 전달합니다.
|
|
292
|
-
|
|
293
|
-
`AUTH_REQUESTED` and `AUTH_WAITING` wait for the user's approval; `AUTH_COMPLETED`, `COLLECTING`, and `COLLECTED` keep polling until a result is available. Billing fields (`charged`, `success`) do not indicate completion. Apply `expiresAt` only while awaiting approval, then use the overall waiting limit. The example's 10-minute limit is a client policy; configure the SDK's `timeoutMs` separately to bound each in-flight call. `AUTH_WAIT_TIMEOUT`, `CLIENT_POLL_TIMEOUT`, `RESULT_NOT_AVAILABLE`, `INVALID_RESULT`, and `UNKNOWN_STATUS` are local example errors that prevent endless polling on inconsistent responses or unknown states. Transport errors propagate without retries.
|
|
294
|
-
|
|
295
|
-
<!-- simple-auth-usage:start -->
|
|
296
|
-
```js
|
|
297
|
-
const accepted = await apick.requestEmployment({
|
|
298
|
-
name: '홍길동',
|
|
299
|
-
birthDate: '19900101',
|
|
300
|
-
phone: '01011112222',
|
|
301
|
-
authProvider: 'kakao',
|
|
302
|
-
insuranceYears: 3
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
const result = await pollDataResult(id => apick.getEmployment(id), accepted);
|
|
307
|
-
if (result.data.status === 'PARTIAL_SUCCESS') console.warn(result.data.sources);
|
|
308
|
-
console.log(result.data.result);
|
|
309
|
-
} catch (error) {
|
|
310
|
-
if ((error.serviceCode || error.code) === 'RESULT_EXPIRED') {
|
|
311
|
-
console.error('결과 보관 기간 만료: 사용자 확인 후 새 인증을 요청하세요.');
|
|
312
|
-
} else {
|
|
313
|
-
throw error;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
<!-- simple-auth-usage:end -->
|
|
318
|
-
|
|
319
|
-
근거: [APICK 개발가이드](https://apick.app/dev_guide/data_health_checkup) · [MCP 3.5.0 상태 계약](https://github.com/lead788/apick-mcp/blob/a803abcb81d07377c85f49bb0b670baf0c17ed04/TOOLS.md)
|
|
280
|
+
본인 간편인증이 필요한 조회 상품(재직·소득·연금·면허·건강검진·현금영수증·국세 신고내역)은 접수(`request*`)와 결과 조회(`get*`)가 분리되어 있습니다. 접수 응답의 `transactionId`로 결과를 폴링하세요.
|
|
281
|
+
|
|
282
|
+
Products that require the user's own simple-auth verification (employment, income, pension, driver's license, health checkup, cash receipts, tax returns) split the call into a `request*()` acceptance and a `get*()` poll. Use the `transactionId` from the accepted response to poll for the result.
|
|
283
|
+
|
|
284
|
+
아래 함수는 7종 모두에 공통으로 사용합니다. 같은 `transactionId`로 순차 조회하며 5→10→20→30초 간격으로 늘린 뒤 30초를 유지합니다. `resultAvailable === true`이면 즉시 결과를 반환합니다. `SUCCESS`는 전체 성공, `PARTIAL_SUCCESS`는 부분 성공이므로 `sources`에서 누락·실패 항목을 확인하세요. `AUTH_REJECTED`·`AUTH_EXPIRED`·`FAILED`는 실패 종료이며, `errorCode: 'RESULT_EXPIRED'`는 결과 보관 기간 만료입니다. 실패·만료 시 자동으로 재접수하지 않습니다.
|
|
285
|
+
|
|
286
|
+
Use this helper for all seven products. Poll sequentially with the same `transactionId`, waiting 5→10→20→30 seconds and then keeping the 30-second interval. Return the result immediately when `resultAvailable === true`. `SUCCESS` means full success; `PARTIAL_SUCCESS` means partial success, so inspect `sources` for missing or failed items. `AUTH_REJECTED`, `AUTH_EXPIRED`, and `FAILED` are terminal failures; `errorCode: 'RESULT_EXPIRED'` means the retained result has expired. Never resubmit automatically after failure or expiry.
|
|
287
|
+
|
|
288
|
+
<!-- simple-auth-polling:start -->
|
|
289
|
+
```js
|
|
290
|
+
async function pollDataResult(getResult, accepted, { timeoutMs = 600_000 } = {}) {
|
|
291
|
+
const pending = new Set([
|
|
292
|
+
'AUTH_REQUESTED', 'AUTH_WAITING', 'AUTH_COMPLETED', 'COLLECTING', 'COLLECTED'
|
|
293
|
+
]);
|
|
294
|
+
const failed = new Set(['AUTH_REJECTED', 'AUTH_EXPIRED', 'FAILED']);
|
|
295
|
+
const completed = new Set(['SUCCESS', 'PARTIAL_SUCCESS']);
|
|
296
|
+
const delays = [5_000, 10_000, 20_000, 30_000];
|
|
297
|
+
const deadline = Date.now() + timeoutMs;
|
|
298
|
+
const transactionId = accepted.data.transactionId;
|
|
299
|
+
const stop = code => { throw Object.assign(new Error(code), { code }); };
|
|
300
|
+
let response = accepted;
|
|
301
|
+
let attempt = 0;
|
|
302
|
+
|
|
303
|
+
for (;;) {
|
|
304
|
+
const data = response.data;
|
|
305
|
+
if (data.errorCode === 'RESULT_EXPIRED') stop('RESULT_EXPIRED');
|
|
306
|
+
if (failed.has(data.status)) stop(data.errorCode || data.status);
|
|
307
|
+
if (data.resultAvailable === true) {
|
|
308
|
+
if (data.result == null) stop('INVALID_RESULT');
|
|
309
|
+
return response;
|
|
310
|
+
}
|
|
311
|
+
if (completed.has(data.status)) stop('RESULT_NOT_AVAILABLE');
|
|
312
|
+
if (!pending.has(data.status)) stop('UNKNOWN_STATUS');
|
|
313
|
+
|
|
314
|
+
const awaitingApproval = ['AUTH_REQUESTED', 'AUTH_WAITING'].includes(data.status);
|
|
315
|
+
const authDeadline = Date.parse(data.expiresAt || accepted.data.expiresAt);
|
|
316
|
+
const limit = awaitingApproval && Number.isFinite(authDeadline)
|
|
317
|
+
? Math.min(deadline, authDeadline) : deadline;
|
|
318
|
+
const timeoutCode = awaitingApproval && limit === authDeadline
|
|
319
|
+
? 'AUTH_WAIT_TIMEOUT' : 'CLIENT_POLL_TIMEOUT';
|
|
320
|
+
const remaining = limit - Date.now();
|
|
321
|
+
if (remaining <= 0) stop(timeoutCode);
|
|
322
|
+
await new Promise(resolve => setTimeout(resolve,
|
|
323
|
+
Math.min(delays[Math.min(attempt++, delays.length - 1)], remaining)));
|
|
324
|
+
if (Date.now() >= limit) stop(timeoutCode);
|
|
325
|
+
response = await getResult(transactionId);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
<!-- simple-auth-polling:end -->
|
|
330
|
+
|
|
331
|
+
`AUTH_REQUESTED`·`AUTH_WAITING`은 사용자 승인을 기다리고, `AUTH_COMPLETED`·`COLLECTING`·`COLLECTED`는 결과가 준비될 때까지 계속 조회합니다. 과금 여부(`charged`, `success`)는 완료 기준이 아닙니다. 인증 대기에만 `expiresAt`을 적용하고 승인 후에는 전체 대기 한도를 적용합니다. 예제의 10분 한도는 클라이언트 정책이며, 진행 중인 호출의 제한 시간은 SDK의 `timeoutMs`로 별도 설정하세요. `AUTH_WAIT_TIMEOUT`·`CLIENT_POLL_TIMEOUT`·`RESULT_NOT_AVAILABLE`·`INVALID_RESULT`·`UNKNOWN_STATUS`는 예제에서 만드는 로컬 오류로, 응답 모순이나 알 수 없는 상태에서 무한 반복하지 않습니다. 통신 오류도 재시도 없이 호출자에게 전달합니다.
|
|
332
|
+
|
|
333
|
+
`AUTH_REQUESTED` and `AUTH_WAITING` wait for the user's approval; `AUTH_COMPLETED`, `COLLECTING`, and `COLLECTED` keep polling until a result is available. Billing fields (`charged`, `success`) do not indicate completion. Apply `expiresAt` only while awaiting approval, then use the overall waiting limit. The example's 10-minute limit is a client policy; configure the SDK's `timeoutMs` separately to bound each in-flight call. `AUTH_WAIT_TIMEOUT`, `CLIENT_POLL_TIMEOUT`, `RESULT_NOT_AVAILABLE`, `INVALID_RESULT`, and `UNKNOWN_STATUS` are local example errors that prevent endless polling on inconsistent responses or unknown states. Transport errors propagate without retries.
|
|
334
|
+
|
|
335
|
+
<!-- simple-auth-usage:start -->
|
|
336
|
+
```js
|
|
337
|
+
const accepted = await apick.requestEmployment({
|
|
338
|
+
name: '홍길동',
|
|
339
|
+
birthDate: '19900101',
|
|
340
|
+
phone: '01011112222',
|
|
341
|
+
authProvider: 'kakao',
|
|
342
|
+
insuranceYears: 3
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const result = await pollDataResult(id => apick.getEmployment(id), accepted);
|
|
347
|
+
if (result.data.status === 'PARTIAL_SUCCESS') console.warn(result.data.sources);
|
|
348
|
+
console.log(result.data.result);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if ((error.serviceCode || error.code) === 'RESULT_EXPIRED') {
|
|
351
|
+
console.error('결과 보관 기간 만료: 사용자 확인 후 새 인증을 요청하세요.');
|
|
352
|
+
} else {
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
<!-- simple-auth-usage:end -->
|
|
358
|
+
|
|
359
|
+
근거: [APICK 개발가이드](https://apick.app/dev_guide/data_health_checkup) · [MCP 3.5.0 상태 계약](https://github.com/lead788/apick-mcp/blob/a803abcb81d07377c85f49bb0b670baf0c17ed04/TOOLS.md)
|
|
320
360
|
|
|
321
361
|
지원 간편인증 방식(`authProvider`) 13종은 `AUTH_PROVIDERS`로 제공됩니다: `kakao`, `naver`, `toss`, `pass`, `samsung`, `kb`, `shinhan`, `hana`, `woori`, `ibk`, `nh`, `kakaobank`, `banksalad`.
|
|
322
362
|
The 13 supported `authProvider` values are exported as `AUTH_PROVIDERS`: `kakao`, `naver`, `toss`, `pass`, `samsung`, `kb`, `shinhan`, `hana`, `woori`, `ibk`, `nh`, `kakaobank`, `banksalad`.
|
|
@@ -331,6 +371,8 @@ Acceptance is billed at a flat rate; the result is billed only on its first succ
|
|
|
331
371
|
| 국민연금 가입내역 / NPS join history | `requestNpsJoinHistory` / `getNpsJoinHistory` | `from`, `to` (`YYYY-MM`) |
|
|
332
372
|
| 운전면허 조회 / Driver's license | `requestDrivingLicense` / `getDrivingLicense` | — |
|
|
333
373
|
| 국가 건강검진 결과 / Health checkup | `requestHealthCheckup` / `getHealthCheckup` | — |
|
|
374
|
+
| 현금영수증 소득공제 내역 / Cash receipt deductions | `requestCashReceiptDeduction` / `getCashReceiptDeduction` | `incomeYears` (1–3) |
|
|
375
|
+
| 국세 신고내역 조회 / Tax return history | `requestTaxReturnHistory` / `getTaxReturnHistory` | `years` (1–10) |
|
|
334
376
|
|
|
335
377
|
## 오류 처리 / Error handling
|
|
336
378
|
|
package/docs/guide.en.md
CHANGED
|
@@ -94,6 +94,13 @@ TTS supports 16 voice IDs. Use `TTS_VOICE_IDS` and the developer guide for the c
|
|
|
94
94
|
const screenshot = await client.screenshot('https://example.com');
|
|
95
95
|
await screenshot.save('./example.jpeg');
|
|
96
96
|
|
|
97
|
+
// Public YouTube videos: a video URL or the 11-character video ID
|
|
98
|
+
const video = await client.youtubeMetadata('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
|
99
|
+
const tracks = await client.youtubeSubtitleList(video.data.video_id);
|
|
100
|
+
const subtitle = await client.youtubeSubtitle(video.data.video_id, 'en', { format: 'srt' });
|
|
101
|
+
await subtitle.save('./' + subtitle.filename);
|
|
102
|
+
await (await client.youtubeThumbnail(video.data.video_id)).save('./thumbnail.jpg');
|
|
103
|
+
|
|
97
104
|
const created = await client.createTtsJob('오늘의 이야기를 시작합니다.', { voiceId: 'v2_ann_m_30s_01' });
|
|
98
105
|
const jobId = created.data.job_id;
|
|
99
106
|
let job = await client.getTtsJob(jobId);
|
|
@@ -187,81 +194,123 @@ The document-specific methods are `maskResidenceCard`, `maskPassport`, `maskIdCa
|
|
|
187
194
|
|
|
188
195
|
## Simple-auth data lookups
|
|
189
196
|
|
|
190
|
-
Employment, income, pension, driver's license,
|
|
191
|
-
|
|
192
|
-
Use this helper for all
|
|
193
|
-
|
|
194
|
-
<!-- simple-auth-polling:start -->
|
|
195
|
-
```js
|
|
196
|
-
async function pollDataResult(getResult, accepted, { timeoutMs = 600_000 } = {}) {
|
|
197
|
-
const pending = new Set([
|
|
198
|
-
'AUTH_REQUESTED', 'AUTH_WAITING', 'AUTH_COMPLETED', 'COLLECTING', 'COLLECTED'
|
|
199
|
-
]);
|
|
200
|
-
const failed = new Set(['AUTH_REJECTED', 'AUTH_EXPIRED', 'FAILED']);
|
|
201
|
-
const completed = new Set(['SUCCESS', 'PARTIAL_SUCCESS']);
|
|
202
|
-
const delays = [5_000, 10_000, 20_000, 30_000];
|
|
203
|
-
const deadline = Date.now() + timeoutMs;
|
|
204
|
-
const transactionId = accepted.data.transactionId;
|
|
205
|
-
const stop = code => { throw Object.assign(new Error(code), { code }); };
|
|
206
|
-
let response = accepted;
|
|
207
|
-
let attempt = 0;
|
|
208
|
-
|
|
209
|
-
for (;;) {
|
|
210
|
-
const data = response.data;
|
|
211
|
-
if (data.errorCode === 'RESULT_EXPIRED') stop('RESULT_EXPIRED');
|
|
212
|
-
if (failed.has(data.status)) stop(data.errorCode || data.status);
|
|
213
|
-
if (data.resultAvailable === true) {
|
|
214
|
-
if (data.result == null) stop('INVALID_RESULT');
|
|
215
|
-
return response;
|
|
216
|
-
}
|
|
217
|
-
if (completed.has(data.status)) stop('RESULT_NOT_AVAILABLE');
|
|
218
|
-
if (!pending.has(data.status)) stop('UNKNOWN_STATUS');
|
|
219
|
-
|
|
220
|
-
const awaitingApproval = ['AUTH_REQUESTED', 'AUTH_WAITING'].includes(data.status);
|
|
221
|
-
const authDeadline = Date.parse(data.expiresAt || accepted.data.expiresAt);
|
|
222
|
-
const limit = awaitingApproval && Number.isFinite(authDeadline)
|
|
223
|
-
? Math.min(deadline, authDeadline) : deadline;
|
|
224
|
-
const timeoutCode = awaitingApproval && limit === authDeadline
|
|
225
|
-
? 'AUTH_WAIT_TIMEOUT' : 'CLIENT_POLL_TIMEOUT';
|
|
226
|
-
const remaining = limit - Date.now();
|
|
227
|
-
if (remaining <= 0) stop(timeoutCode);
|
|
228
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
229
|
-
Math.min(delays[Math.min(attempt++, delays.length - 1)], remaining)));
|
|
230
|
-
if (Date.now() >= limit) stop(timeoutCode);
|
|
231
|
-
response = await getResult(transactionId);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
<!-- simple-auth-polling:end -->
|
|
236
|
-
|
|
237
|
-
`AUTH_REQUESTED` and `AUTH_WAITING` wait for the user's approval; `AUTH_COMPLETED`, `COLLECTING`, and `COLLECTED` keep polling until a result is available. Billing fields (`charged`, `success`) do not indicate completion. Apply `expiresAt` only while awaiting approval, then use the overall waiting limit. The example's 10-minute limit is a client policy; configure the SDK's `timeoutMs` separately to bound each in-flight call. `AUTH_WAIT_TIMEOUT`, `CLIENT_POLL_TIMEOUT`, `RESULT_NOT_AVAILABLE`, `INVALID_RESULT`, and `UNKNOWN_STATUS` are local example errors that prevent endless polling on inconsistent responses or unknown states. Transport errors propagate without retries.
|
|
238
|
-
|
|
239
|
-
<!-- simple-auth-usage:start -->
|
|
240
|
-
```js
|
|
241
|
-
const accepted = await client.requestDrivingLicense({
|
|
242
|
-
name: 'Hong Gildong',
|
|
243
|
-
birthDate: '19900101',
|
|
244
|
-
phone: '01011112222',
|
|
245
|
-
authProvider: 'kakao'
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
const result = await pollDataResult(id => client.getDrivingLicense(id), accepted);
|
|
250
|
-
if (result.data.status === 'PARTIAL_SUCCESS') console.warn(result.data.sources);
|
|
251
|
-
console.log(result.data.result);
|
|
252
|
-
} catch (error) {
|
|
253
|
-
if ((error.serviceCode || error.code) === 'RESULT_EXPIRED') {
|
|
254
|
-
console.error('Result expired. Ask the user before starting a new authentication request.');
|
|
255
|
-
} else {
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
<!-- simple-auth-usage:end -->
|
|
261
|
-
|
|
262
|
-
Sources: [APICK development guide](https://apick.app/dev_guide/data_health_checkup) · [MCP 3.5.0 status contract](https://github.com/lead788/apick-mcp/blob/a803abcb81d07377c85f49bb0b670baf0c17ed04/TOOLS.md)
|
|
263
|
-
|
|
264
|
-
`authProvider` is one of the 13 values in `AUTH_PROVIDERS` (kakao, naver, toss, pass, samsung, kb, shinhan, hana, woori, ibk, nh, kakaobank, banksalad). Acceptance is billed at a flat rate; the result is billed only on its first return and free to re-poll afterward. `requestEmployment` takes an optional `insuranceYears` (1-3), `requestPersonalIncome` takes `incomeYears` (1-5), and `requestNpsJoinHistory` takes optional `from`/`to` (`YYYY-MM`). The remaining products are `requestDrivingLicense` and `requestHealthCheckup`.
|
|
197
|
+
Employment, income, pension, driver's license, health checkup, cash receipt deduction, and tax return history lookups require the user's own simple-auth verification, so the call is split into acceptance (`request*`) and result polling (`get*`).
|
|
198
|
+
|
|
199
|
+
Use this helper for all seven products. Poll sequentially with the same `transactionId`, waiting 5→10→20→30 seconds and then keeping the 30-second interval. Return the result immediately when `resultAvailable === true`. `SUCCESS` means full success; `PARTIAL_SUCCESS` means partial success, so inspect `sources` for missing or failed items. `AUTH_REJECTED`, `AUTH_EXPIRED`, and `FAILED` are terminal failures; `errorCode: 'RESULT_EXPIRED'` means the retained result has expired. Never resubmit automatically after failure or expiry.
|
|
200
|
+
|
|
201
|
+
<!-- simple-auth-polling:start -->
|
|
202
|
+
```js
|
|
203
|
+
async function pollDataResult(getResult, accepted, { timeoutMs = 600_000 } = {}) {
|
|
204
|
+
const pending = new Set([
|
|
205
|
+
'AUTH_REQUESTED', 'AUTH_WAITING', 'AUTH_COMPLETED', 'COLLECTING', 'COLLECTED'
|
|
206
|
+
]);
|
|
207
|
+
const failed = new Set(['AUTH_REJECTED', 'AUTH_EXPIRED', 'FAILED']);
|
|
208
|
+
const completed = new Set(['SUCCESS', 'PARTIAL_SUCCESS']);
|
|
209
|
+
const delays = [5_000, 10_000, 20_000, 30_000];
|
|
210
|
+
const deadline = Date.now() + timeoutMs;
|
|
211
|
+
const transactionId = accepted.data.transactionId;
|
|
212
|
+
const stop = code => { throw Object.assign(new Error(code), { code }); };
|
|
213
|
+
let response = accepted;
|
|
214
|
+
let attempt = 0;
|
|
215
|
+
|
|
216
|
+
for (;;) {
|
|
217
|
+
const data = response.data;
|
|
218
|
+
if (data.errorCode === 'RESULT_EXPIRED') stop('RESULT_EXPIRED');
|
|
219
|
+
if (failed.has(data.status)) stop(data.errorCode || data.status);
|
|
220
|
+
if (data.resultAvailable === true) {
|
|
221
|
+
if (data.result == null) stop('INVALID_RESULT');
|
|
222
|
+
return response;
|
|
223
|
+
}
|
|
224
|
+
if (completed.has(data.status)) stop('RESULT_NOT_AVAILABLE');
|
|
225
|
+
if (!pending.has(data.status)) stop('UNKNOWN_STATUS');
|
|
226
|
+
|
|
227
|
+
const awaitingApproval = ['AUTH_REQUESTED', 'AUTH_WAITING'].includes(data.status);
|
|
228
|
+
const authDeadline = Date.parse(data.expiresAt || accepted.data.expiresAt);
|
|
229
|
+
const limit = awaitingApproval && Number.isFinite(authDeadline)
|
|
230
|
+
? Math.min(deadline, authDeadline) : deadline;
|
|
231
|
+
const timeoutCode = awaitingApproval && limit === authDeadline
|
|
232
|
+
? 'AUTH_WAIT_TIMEOUT' : 'CLIENT_POLL_TIMEOUT';
|
|
233
|
+
const remaining = limit - Date.now();
|
|
234
|
+
if (remaining <= 0) stop(timeoutCode);
|
|
235
|
+
await new Promise(resolve => setTimeout(resolve,
|
|
236
|
+
Math.min(delays[Math.min(attempt++, delays.length - 1)], remaining)));
|
|
237
|
+
if (Date.now() >= limit) stop(timeoutCode);
|
|
238
|
+
response = await getResult(transactionId);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
<!-- simple-auth-polling:end -->
|
|
243
|
+
|
|
244
|
+
`AUTH_REQUESTED` and `AUTH_WAITING` wait for the user's approval; `AUTH_COMPLETED`, `COLLECTING`, and `COLLECTED` keep polling until a result is available. Billing fields (`charged`, `success`) do not indicate completion. Apply `expiresAt` only while awaiting approval, then use the overall waiting limit. The example's 10-minute limit is a client policy; configure the SDK's `timeoutMs` separately to bound each in-flight call. `AUTH_WAIT_TIMEOUT`, `CLIENT_POLL_TIMEOUT`, `RESULT_NOT_AVAILABLE`, `INVALID_RESULT`, and `UNKNOWN_STATUS` are local example errors that prevent endless polling on inconsistent responses or unknown states. Transport errors propagate without retries.
|
|
245
|
+
|
|
246
|
+
<!-- simple-auth-usage:start -->
|
|
247
|
+
```js
|
|
248
|
+
const accepted = await client.requestDrivingLicense({
|
|
249
|
+
name: 'Hong Gildong',
|
|
250
|
+
birthDate: '19900101',
|
|
251
|
+
phone: '01011112222',
|
|
252
|
+
authProvider: 'kakao'
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const result = await pollDataResult(id => client.getDrivingLicense(id), accepted);
|
|
257
|
+
if (result.data.status === 'PARTIAL_SUCCESS') console.warn(result.data.sources);
|
|
258
|
+
console.log(result.data.result);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if ((error.serviceCode || error.code) === 'RESULT_EXPIRED') {
|
|
261
|
+
console.error('Result expired. Ask the user before starting a new authentication request.');
|
|
262
|
+
} else {
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
<!-- simple-auth-usage:end -->
|
|
268
|
+
|
|
269
|
+
Sources: [APICK development guide](https://apick.app/dev_guide/data_health_checkup) · [MCP 3.5.0 status contract](https://github.com/lead788/apick-mcp/blob/a803abcb81d07377c85f49bb0b670baf0c17ed04/TOOLS.md)
|
|
270
|
+
|
|
271
|
+
`authProvider` is one of the 13 values in `AUTH_PROVIDERS` (kakao, naver, toss, pass, samsung, kb, shinhan, hana, woori, ibk, nh, kakaobank, banksalad). Acceptance is billed at a flat rate; the result is billed only on its first return and free to re-poll afterward. `requestEmployment` takes an optional `insuranceYears` (1-3), `requestPersonalIncome` takes `incomeYears` (1-5), and `requestNpsJoinHistory` takes optional `from`/`to` (`YYYY-MM`). `requestCashReceiptDeduction` takes optional `incomeYears` (1-3) and `requestTaxReturnHistory` takes optional `years` (1-10). The remaining products are `requestDrivingLicense` and `requestHealthCheckup`.
|
|
272
|
+
|
|
273
|
+
## Skills
|
|
274
|
+
|
|
275
|
+
Search and run reviewed Skills. Each Skill fixes its input format (`input_schema`), output format (`output_schema`) and base amount (`price_points`). A run is charged only when a result in the promised format is returned.
|
|
276
|
+
|
|
277
|
+
For Skills that use generative AI (`usage_priced: true`) the actual usage of each run is added to the base amount, so the charge varies per run. `quoteSkill()` returns the estimated amount (`estimated_points`) and the maximum amount (`max_points`). The maximum is reserved when the run is accepted; when it finishes only the actual amount is charged and the rest is returned. Read the actual charge from `run.billing.charged_points`.
|
|
278
|
+
|
|
279
|
+
```js
|
|
280
|
+
import { randomUUID } from 'node:crypto';
|
|
281
|
+
|
|
282
|
+
const found = await client.searchSkills({ query: 'product name', limit: 5 });
|
|
283
|
+
const skillId = found.data.items[0].skill_id;
|
|
284
|
+
const detail = await client.getSkill(skillId);
|
|
285
|
+
const input = { product_name: 'Sturdy folding umbrella' };
|
|
286
|
+
|
|
287
|
+
const quote = await client.quoteSkill(skillId, input); // free, valid for 5 minutes
|
|
288
|
+
const idempotencyKey = randomUUID(); // reuse the same value when retrying
|
|
289
|
+
let run = (await client.runSkill(skillId, input, {
|
|
290
|
+
idempotencyKey, quoteId: quote.data.quote_id, maxCostPoints: quote.data.max_points
|
|
291
|
+
})).data;
|
|
292
|
+
while (!['succeeded', 'failed', 'timed_out', 'cancelled'].includes(run.status)) {
|
|
293
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
294
|
+
run = (await client.getSkillRun(run.run_id)).data;
|
|
295
|
+
}
|
|
296
|
+
console.log(run.status, run.billing, run.result);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
| Method | Description |
|
|
300
|
+
| --- | --- |
|
|
301
|
+
| `searchSkills({ query, category, cursor, limit })` | Search. `limit` 1-20; `category` is one of `SKILL_CATEGORIES` |
|
|
302
|
+
| `getSkill(skillId)` | Input and output formats, price, limits, examples |
|
|
303
|
+
| `quoteSkill(skillId, input, { version })` | Validates the input and reports the points to be charged. Does not run |
|
|
304
|
+
| `runSkill(skillId, input, { idempotencyKey, quoteId, maxCostPoints, version, waitSeconds })` | Run. `idempotencyKey` is required; `waitSeconds` 0-20 (default 20) |
|
|
305
|
+
| `getSkillRun(runId)` / `getSkillRunResult(runId)` | Run status (with the result once succeeded) / result only. Results are kept for 7 days |
|
|
306
|
+
| `cancelSkillRun(runId)` | Cancel an unfinished run. Cancelled runs are not charged |
|
|
307
|
+
| `skillUsage({ cursor, limit })` | Your runs. `limit` 1-50 |
|
|
308
|
+
|
|
309
|
+
- Skills responses are returned as-is in `data`. Read the charge from `data.billing.status` (`reserved`, `captured`, `released`, `partially_refunded`, `refunded`); `meta.cost` is not populated.
|
|
310
|
+
- If a run does not finish within 20 seconds it comes back as `queued` or `running`; poll `getSkillRun`.
|
|
311
|
+
- If a response is lost, call again with the same `idempotencyKey`. Points are charged once. The same key with a different input is rejected with `IDEMPOTENCY_CONFLICT`.
|
|
312
|
+
- Rejections before acceptance throw `ApickApiError` (`serviceCode` such as `INVALID_INPUT`, `INSUFFICIENT_POINTS`, `PAYMENT_REQUIRED`, `PRICE_EXCEEDS_LIMIT`, `RATE_LIMITED`; extra information in `details`). Failures after acceptance are reported through `status` and `failure_code` (`EXECUTION_FAILED`, `OUTPUT_INVALID`, `TIMED_OUT`, `CANCELLED`, `UPSTREAM_UNAVAILABLE`).
|
|
313
|
+
- Skills with `uses_generative_ai: true` produce results with generative AI. Verify them before relying on them.
|
|
265
314
|
|
|
266
315
|
## Errors and retries
|
|
267
316
|
|
package/docs/guide.ko.md
CHANGED
|
@@ -94,6 +94,13 @@ TTS는 16개 목소리 ID를 지원합니다. 정확한 목록은 `TTS_VOICE_IDS
|
|
|
94
94
|
const screenshot = await client.screenshot('https://example.com');
|
|
95
95
|
await screenshot.save('./example.jpeg');
|
|
96
96
|
|
|
97
|
+
// 유튜브 공개 영상: 영상 주소 또는 11자리 영상 ID
|
|
98
|
+
const video = await client.youtubeMetadata('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
|
99
|
+
const tracks = await client.youtubeSubtitleList(video.data.video_id);
|
|
100
|
+
const subtitle = await client.youtubeSubtitle(video.data.video_id, 'en', { format: 'srt' });
|
|
101
|
+
await subtitle.save('./' + subtitle.filename);
|
|
102
|
+
await (await client.youtubeThumbnail(video.data.video_id)).save('./thumbnail.jpg');
|
|
103
|
+
|
|
97
104
|
const created = await client.createTtsJob('오늘의 이야기를 시작합니다.', { voiceId: 'v2_ann_m_30s_01' });
|
|
98
105
|
const jobId = created.data.job_id;
|
|
99
106
|
let job = await client.getTtsJob(jobId);
|
|
@@ -189,81 +196,123 @@ console.log(result.data.result.fields);
|
|
|
189
196
|
|
|
190
197
|
## 간편인증 데이터 조회
|
|
191
198
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
아래 함수는
|
|
195
|
-
|
|
196
|
-
<!-- simple-auth-polling:start -->
|
|
197
|
-
```js
|
|
198
|
-
async function pollDataResult(getResult, accepted, { timeoutMs = 600_000 } = {}) {
|
|
199
|
-
const pending = new Set([
|
|
200
|
-
'AUTH_REQUESTED', 'AUTH_WAITING', 'AUTH_COMPLETED', 'COLLECTING', 'COLLECTED'
|
|
201
|
-
]);
|
|
202
|
-
const failed = new Set(['AUTH_REJECTED', 'AUTH_EXPIRED', 'FAILED']);
|
|
203
|
-
const completed = new Set(['SUCCESS', 'PARTIAL_SUCCESS']);
|
|
204
|
-
const delays = [5_000, 10_000, 20_000, 30_000];
|
|
205
|
-
const deadline = Date.now() + timeoutMs;
|
|
206
|
-
const transactionId = accepted.data.transactionId;
|
|
207
|
-
const stop = code => { throw Object.assign(new Error(code), { code }); };
|
|
208
|
-
let response = accepted;
|
|
209
|
-
let attempt = 0;
|
|
210
|
-
|
|
211
|
-
for (;;) {
|
|
212
|
-
const data = response.data;
|
|
213
|
-
if (data.errorCode === 'RESULT_EXPIRED') stop('RESULT_EXPIRED');
|
|
214
|
-
if (failed.has(data.status)) stop(data.errorCode || data.status);
|
|
215
|
-
if (data.resultAvailable === true) {
|
|
216
|
-
if (data.result == null) stop('INVALID_RESULT');
|
|
217
|
-
return response;
|
|
218
|
-
}
|
|
219
|
-
if (completed.has(data.status)) stop('RESULT_NOT_AVAILABLE');
|
|
220
|
-
if (!pending.has(data.status)) stop('UNKNOWN_STATUS');
|
|
221
|
-
|
|
222
|
-
const awaitingApproval = ['AUTH_REQUESTED', 'AUTH_WAITING'].includes(data.status);
|
|
223
|
-
const authDeadline = Date.parse(data.expiresAt || accepted.data.expiresAt);
|
|
224
|
-
const limit = awaitingApproval && Number.isFinite(authDeadline)
|
|
225
|
-
? Math.min(deadline, authDeadline) : deadline;
|
|
226
|
-
const timeoutCode = awaitingApproval && limit === authDeadline
|
|
227
|
-
? 'AUTH_WAIT_TIMEOUT' : 'CLIENT_POLL_TIMEOUT';
|
|
228
|
-
const remaining = limit - Date.now();
|
|
229
|
-
if (remaining <= 0) stop(timeoutCode);
|
|
230
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
231
|
-
Math.min(delays[Math.min(attempt++, delays.length - 1)], remaining)));
|
|
232
|
-
if (Date.now() >= limit) stop(timeoutCode);
|
|
233
|
-
response = await getResult(transactionId);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
<!-- simple-auth-polling:end -->
|
|
238
|
-
|
|
239
|
-
`AUTH_REQUESTED`·`AUTH_WAITING`은 사용자 승인을 기다리고, `AUTH_COMPLETED`·`COLLECTING`·`COLLECTED`는 결과가 준비될 때까지 계속 조회합니다. 과금 여부(`charged`, `success`)는 완료 기준이 아닙니다. 인증 대기에만 `expiresAt`을 적용하고 승인 후에는 전체 대기 한도를 적용합니다. 예제의 10분 한도는 클라이언트 정책이며, 진행 중인 호출의 제한 시간은 SDK의 `timeoutMs`로 별도 설정하세요. `AUTH_WAIT_TIMEOUT`·`CLIENT_POLL_TIMEOUT`·`RESULT_NOT_AVAILABLE`·`INVALID_RESULT`·`UNKNOWN_STATUS`는 예제에서 만드는 로컬 오류로, 응답 모순이나 알 수 없는 상태에서 무한 반복하지 않습니다. 통신 오류도 재시도 없이 호출자에게 전달합니다.
|
|
240
|
-
|
|
241
|
-
<!-- simple-auth-usage:start -->
|
|
242
|
-
```js
|
|
243
|
-
const accepted = await client.requestDrivingLicense({
|
|
244
|
-
name: '홍길동',
|
|
245
|
-
birthDate: '19900101',
|
|
246
|
-
phone: '01011112222',
|
|
247
|
-
authProvider: 'kakao'
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
const result = await pollDataResult(id => client.getDrivingLicense(id), accepted);
|
|
252
|
-
if (result.data.status === 'PARTIAL_SUCCESS') console.warn(result.data.sources);
|
|
253
|
-
console.log(result.data.result);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
if ((error.serviceCode || error.code) === 'RESULT_EXPIRED') {
|
|
256
|
-
console.error('결과 보관 기간 만료: 사용자 확인 후 새 인증을 요청하세요.');
|
|
257
|
-
} else {
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
<!-- simple-auth-usage:end -->
|
|
263
|
-
|
|
264
|
-
근거: [APICK 개발가이드](https://apick.app/dev_guide/data_health_checkup) · [MCP 3.5.0 상태 계약](https://github.com/lead788/apick-mcp/blob/a803abcb81d07377c85f49bb0b670baf0c17ed04/TOOLS.md)
|
|
265
|
-
|
|
266
|
-
`authProvider`는 `AUTH_PROVIDERS`(13종: kakao, naver, toss, pass, samsung, kb, shinhan, hana, woori, ibk, nh, kakaobank, banksalad) 중 하나입니다. 접수는 정액 과금, 결과는 최초 반환에서만 과금되며 재조회는 무료입니다. `requestEmployment`는 `insuranceYears`(1~3), `requestPersonalIncome`은 `incomeYears`(1~5), `requestNpsJoinHistory`는 `from`/`to`(`YYYY-MM`) 선택 입력을 받습니다. 나머지 상품은 `requestDrivingLicense`, `requestHealthCheckup`입니다.
|
|
199
|
+
재직·소득·연금·면허·건강검진·현금영수증·국세 신고내역 조회는 본인 간편인증이 필요해 접수(`request*`)와 결과 조회(`get*`)가 나뉩니다.
|
|
200
|
+
|
|
201
|
+
아래 함수는 7종 모두에 공통으로 사용합니다. 같은 `transactionId`로 순차 조회하며 5→10→20→30초 간격으로 늘린 뒤 30초를 유지합니다. `resultAvailable === true`이면 즉시 결과를 반환합니다. `SUCCESS`는 전체 성공, `PARTIAL_SUCCESS`는 부분 성공이므로 `sources`에서 누락·실패 항목을 확인하세요. `AUTH_REJECTED`·`AUTH_EXPIRED`·`FAILED`는 실패 종료이며, `errorCode: 'RESULT_EXPIRED'`는 결과 보관 기간 만료입니다. 실패·만료 시 자동으로 재접수하지 않습니다.
|
|
202
|
+
|
|
203
|
+
<!-- simple-auth-polling:start -->
|
|
204
|
+
```js
|
|
205
|
+
async function pollDataResult(getResult, accepted, { timeoutMs = 600_000 } = {}) {
|
|
206
|
+
const pending = new Set([
|
|
207
|
+
'AUTH_REQUESTED', 'AUTH_WAITING', 'AUTH_COMPLETED', 'COLLECTING', 'COLLECTED'
|
|
208
|
+
]);
|
|
209
|
+
const failed = new Set(['AUTH_REJECTED', 'AUTH_EXPIRED', 'FAILED']);
|
|
210
|
+
const completed = new Set(['SUCCESS', 'PARTIAL_SUCCESS']);
|
|
211
|
+
const delays = [5_000, 10_000, 20_000, 30_000];
|
|
212
|
+
const deadline = Date.now() + timeoutMs;
|
|
213
|
+
const transactionId = accepted.data.transactionId;
|
|
214
|
+
const stop = code => { throw Object.assign(new Error(code), { code }); };
|
|
215
|
+
let response = accepted;
|
|
216
|
+
let attempt = 0;
|
|
217
|
+
|
|
218
|
+
for (;;) {
|
|
219
|
+
const data = response.data;
|
|
220
|
+
if (data.errorCode === 'RESULT_EXPIRED') stop('RESULT_EXPIRED');
|
|
221
|
+
if (failed.has(data.status)) stop(data.errorCode || data.status);
|
|
222
|
+
if (data.resultAvailable === true) {
|
|
223
|
+
if (data.result == null) stop('INVALID_RESULT');
|
|
224
|
+
return response;
|
|
225
|
+
}
|
|
226
|
+
if (completed.has(data.status)) stop('RESULT_NOT_AVAILABLE');
|
|
227
|
+
if (!pending.has(data.status)) stop('UNKNOWN_STATUS');
|
|
228
|
+
|
|
229
|
+
const awaitingApproval = ['AUTH_REQUESTED', 'AUTH_WAITING'].includes(data.status);
|
|
230
|
+
const authDeadline = Date.parse(data.expiresAt || accepted.data.expiresAt);
|
|
231
|
+
const limit = awaitingApproval && Number.isFinite(authDeadline)
|
|
232
|
+
? Math.min(deadline, authDeadline) : deadline;
|
|
233
|
+
const timeoutCode = awaitingApproval && limit === authDeadline
|
|
234
|
+
? 'AUTH_WAIT_TIMEOUT' : 'CLIENT_POLL_TIMEOUT';
|
|
235
|
+
const remaining = limit - Date.now();
|
|
236
|
+
if (remaining <= 0) stop(timeoutCode);
|
|
237
|
+
await new Promise(resolve => setTimeout(resolve,
|
|
238
|
+
Math.min(delays[Math.min(attempt++, delays.length - 1)], remaining)));
|
|
239
|
+
if (Date.now() >= limit) stop(timeoutCode);
|
|
240
|
+
response = await getResult(transactionId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
<!-- simple-auth-polling:end -->
|
|
245
|
+
|
|
246
|
+
`AUTH_REQUESTED`·`AUTH_WAITING`은 사용자 승인을 기다리고, `AUTH_COMPLETED`·`COLLECTING`·`COLLECTED`는 결과가 준비될 때까지 계속 조회합니다. 과금 여부(`charged`, `success`)는 완료 기준이 아닙니다. 인증 대기에만 `expiresAt`을 적용하고 승인 후에는 전체 대기 한도를 적용합니다. 예제의 10분 한도는 클라이언트 정책이며, 진행 중인 호출의 제한 시간은 SDK의 `timeoutMs`로 별도 설정하세요. `AUTH_WAIT_TIMEOUT`·`CLIENT_POLL_TIMEOUT`·`RESULT_NOT_AVAILABLE`·`INVALID_RESULT`·`UNKNOWN_STATUS`는 예제에서 만드는 로컬 오류로, 응답 모순이나 알 수 없는 상태에서 무한 반복하지 않습니다. 통신 오류도 재시도 없이 호출자에게 전달합니다.
|
|
247
|
+
|
|
248
|
+
<!-- simple-auth-usage:start -->
|
|
249
|
+
```js
|
|
250
|
+
const accepted = await client.requestDrivingLicense({
|
|
251
|
+
name: '홍길동',
|
|
252
|
+
birthDate: '19900101',
|
|
253
|
+
phone: '01011112222',
|
|
254
|
+
authProvider: 'kakao'
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const result = await pollDataResult(id => client.getDrivingLicense(id), accepted);
|
|
259
|
+
if (result.data.status === 'PARTIAL_SUCCESS') console.warn(result.data.sources);
|
|
260
|
+
console.log(result.data.result);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if ((error.serviceCode || error.code) === 'RESULT_EXPIRED') {
|
|
263
|
+
console.error('결과 보관 기간 만료: 사용자 확인 후 새 인증을 요청하세요.');
|
|
264
|
+
} else {
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
<!-- simple-auth-usage:end -->
|
|
270
|
+
|
|
271
|
+
근거: [APICK 개발가이드](https://apick.app/dev_guide/data_health_checkup) · [MCP 3.5.0 상태 계약](https://github.com/lead788/apick-mcp/blob/a803abcb81d07377c85f49bb0b670baf0c17ed04/TOOLS.md)
|
|
272
|
+
|
|
273
|
+
`authProvider`는 `AUTH_PROVIDERS`(13종: kakao, naver, toss, pass, samsung, kb, shinhan, hana, woori, ibk, nh, kakaobank, banksalad) 중 하나입니다. 접수는 정액 과금, 결과는 최초 반환에서만 과금되며 재조회는 무료입니다. `requestEmployment`는 `insuranceYears`(1~3), `requestPersonalIncome`은 `incomeYears`(1~5), `requestNpsJoinHistory`는 `from`/`to`(`YYYY-MM`) 선택 입력을 받습니다. `requestCashReceiptDeduction`은 `incomeYears`(1~3), `requestTaxReturnHistory`는 `years`(1~10) 선택 입력을 받습니다. 나머지 상품은 `requestDrivingLicense`, `requestHealthCheckup`입니다.
|
|
274
|
+
|
|
275
|
+
## Skills
|
|
276
|
+
|
|
277
|
+
검수를 거친 Skill 을 검색하고 실행합니다. Skill 마다 입력 형식(`input_schema`)·결과 형식(`output_schema`)·기본 금액(`price_points`)이 정해져 있으며, 결과가 약속한 형식으로 반환된 실행만 차감됩니다.
|
|
278
|
+
|
|
279
|
+
생성형 AI 를 쓰는 Skill(`usage_priced: true`)은 기본 금액에 그 실행의 실제 사용량이 더해져 실행마다 금액이 달라집니다. `quoteSkill()` 은 예상 금액(`estimated_points`)과 최대 금액(`max_points`)을 돌려줍니다. 실행을 접수할 때 최대 금액을 예약하고, 끝나면 실제 금액만 차감한 뒤 나머지를 돌려줍니다. 실제 차감액은 `run.billing.charged_points` 에서 확인합니다.
|
|
280
|
+
|
|
281
|
+
```js
|
|
282
|
+
import { randomUUID } from 'node:crypto';
|
|
283
|
+
|
|
284
|
+
const found = await client.searchSkills({ query: '상품명', limit: 5 });
|
|
285
|
+
const skillId = found.data.items[0].skill_id;
|
|
286
|
+
const detail = await client.getSkill(skillId);
|
|
287
|
+
const input = { product_name: '튼튼한 접이식 우산' };
|
|
288
|
+
|
|
289
|
+
const quote = await client.quoteSkill(skillId, input); // 무료. 5분 동안 유효
|
|
290
|
+
const idempotencyKey = randomUUID(); // 재시도할 때 같은 값을 다시 씁니다
|
|
291
|
+
let run = (await client.runSkill(skillId, input, {
|
|
292
|
+
idempotencyKey, quoteId: quote.data.quote_id, maxCostPoints: quote.data.max_points
|
|
293
|
+
})).data;
|
|
294
|
+
while (!['succeeded', 'failed', 'timed_out', 'cancelled'].includes(run.status)) {
|
|
295
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
296
|
+
run = (await client.getSkillRun(run.run_id)).data;
|
|
297
|
+
}
|
|
298
|
+
console.log(run.status, run.billing, run.result);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
| 메서드 | 설명 |
|
|
302
|
+
| --- | --- |
|
|
303
|
+
| `searchSkills({ query, category, cursor, limit })` | 검색. `limit` 1~20, `category` 는 `SKILL_CATEGORIES` 중 하나 |
|
|
304
|
+
| `getSkill(skillId)` | 입력·결과 형식, 가격, 처리 상한, 예제 |
|
|
305
|
+
| `quoteSkill(skillId, input, { version })` | 입력 검사와 차감될 포인트 확인. 실행하지 않습니다 |
|
|
306
|
+
| `runSkill(skillId, input, { idempotencyKey, quoteId, maxCostPoints, version, waitSeconds })` | 실행. `idempotencyKey` 필수, `waitSeconds` 0~20(기본 20) |
|
|
307
|
+
| `getSkillRun(runId)` / `getSkillRunResult(runId)` | 실행 상태(성공하면 결과 포함) / 결과만 조회. 결과는 7일 동안 보관 |
|
|
308
|
+
| `cancelSkillRun(runId)` | 끝나지 않은 실행 취소. 취소된 실행은 차감되지 않습니다 |
|
|
309
|
+
| `skillUsage({ cursor, limit })` | 내 실행 내역. `limit` 1~50 |
|
|
310
|
+
|
|
311
|
+
- Skills 응답은 봉투 없이 `data` 에 그대로 담기며, 과금 상태는 `data.billing.status`(`reserved`·`captured`·`released`·`partially_refunded`·`refunded`)로 확인합니다. `meta.cost` 는 채워지지 않습니다.
|
|
312
|
+
- 20초 안에 끝나지 않으면 `status` 가 `queued`·`running` 인 채로 돌아오므로 `getSkillRun` 으로 확인하세요.
|
|
313
|
+
- 응답을 받지 못했을 때는 같은 `idempotencyKey` 로 다시 호출하세요. 포인트는 한 번만 차감됩니다. 같은 키에 다른 입력을 보내면 `IDEMPOTENCY_CONFLICT` 입니다.
|
|
314
|
+
- 접수 전 거절은 `ApickApiError`(`serviceCode`: `INVALID_INPUT`·`INSUFFICIENT_POINTS`·`PAYMENT_REQUIRED`·`PRICE_EXCEEDS_LIMIT`·`RATE_LIMITED` 등, `details` 에 부가 정보)로, 접수 뒤 실패는 `status` 와 `failure_code`(`EXECUTION_FAILED`·`OUTPUT_INVALID`·`TIMED_OUT`·`CANCELLED`·`UPSTREAM_UNAVAILABLE`)로 확인합니다.
|
|
315
|
+
- `uses_generative_ai` 가 true 인 Skill 은 생성형 AI 로 결과를 만듭니다. 중요한 판단에 쓰기 전에 확인하세요.
|
|
267
316
|
|
|
268
317
|
## 오류와 재시도
|
|
269
318
|
|
package/package.json
CHANGED
package/src/index.cjs
CHANGED
|
@@ -36,6 +36,10 @@ const SERVICE_DEFINITIONS = Object.freeze({
|
|
|
36
36
|
googleSearch: { endpoint: '/rest/google_search', timeoutMs: 35_000, output: 'json' },
|
|
37
37
|
googleImageSearch: { endpoint: '/rest/google_image_search', timeoutMs: 35_000, output: 'json' },
|
|
38
38
|
screenshot: { endpoint: '/rest/url_screenshot', timeoutMs: 75_000, output: 'binary', filename: 'screenshot.jpeg' },
|
|
39
|
+
youtubeMetadata: { endpoint: '/rest/youtube_metadata', timeoutMs: 60_000, output: 'json' },
|
|
40
|
+
youtubeThumbnail: { endpoint: '/rest/youtube_thumbnail', timeoutMs: 60_000, output: 'binary', filename: 'thumbnail.jpg' },
|
|
41
|
+
youtubeSubtitleList: { endpoint: '/rest/youtube_subtitle_list', timeoutMs: 60_000, output: 'json' },
|
|
42
|
+
youtubeSubtitle: { endpoint: '/rest/youtube_subtitle', timeoutMs: 60_000, output: 'binary', filename: 'subtitle.vtt' },
|
|
39
43
|
createTtsJob: { endpoint: '/rest/tts/jobs', timeoutMs: 35_000, output: 'json' },
|
|
40
44
|
createVideoJob: { endpoint: '/rest/seedance/jobs', timeoutMs: 60_000, output: 'json' },
|
|
41
45
|
htmlToPdf: { endpoint: '/rest/html_to_pdf', timeoutMs: 25_000, output: 'binary', filename: 'document.pdf' },
|
|
@@ -47,7 +51,9 @@ const SERVICE_DEFINITIONS = Object.freeze({
|
|
|
47
51
|
requestPersonalIncome: { endpoint: '/rest/req_personal_income', timeoutMs: 35_000, output: 'json' },
|
|
48
52
|
requestNpsJoinHistory: { endpoint: '/rest/req_nps_join_history', timeoutMs: 35_000, output: 'json' },
|
|
49
53
|
requestDrivingLicense: { endpoint: '/rest/req_driving_license', timeoutMs: 35_000, output: 'json' },
|
|
50
|
-
requestHealthCheckup: { endpoint: '/rest/req_health_checkup', timeoutMs: 35_000, output: 'json' }
|
|
54
|
+
requestHealthCheckup: { endpoint: '/rest/req_health_checkup', timeoutMs: 35_000, output: 'json' },
|
|
55
|
+
requestCashReceiptDeduction: { endpoint: '/rest/req_cash_receipt_deduction', timeoutMs: 35_000, output: 'json' },
|
|
56
|
+
requestTaxReturnHistory: { endpoint: '/rest/req_tax_return_history', timeoutMs: 35_000, output: 'json' }
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
const SERVICES = Object.freeze(Object.fromEntries(
|
|
@@ -169,6 +175,27 @@ function dataRequestInput(input) {
|
|
|
169
175
|
};
|
|
170
176
|
}
|
|
171
177
|
|
|
178
|
+
const SKILL_CATEGORIES = Object.freeze(['data', 'ai', 'dev', 'document', 'marketing', 'finance', 'productivity', 'video', 'etc']);
|
|
179
|
+
const SKILL_CATEGORY_SET = new Set(SKILL_CATEGORIES);
|
|
180
|
+
|
|
181
|
+
function normalizeSkillId(value) {
|
|
182
|
+
return encodeURIComponent(requiredString('skillId', value, 80));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeSkillRunId(value) {
|
|
186
|
+
return encodeURIComponent(requiredString('runId', value, 64));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function skillInput(value) {
|
|
190
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new TypeError('input must be a plain object matching the Skill input_schema.');
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function optionalSkillText(name, value, maxLength) {
|
|
195
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
196
|
+
return requiredString(name, String(value), maxLength);
|
|
197
|
+
}
|
|
198
|
+
|
|
172
199
|
function numberOrNull(value) {
|
|
173
200
|
if (value === undefined || value === null || value === '') return null;
|
|
174
201
|
const number = Number(value);
|
|
@@ -262,6 +289,7 @@ class ApickApiError extends Error {
|
|
|
262
289
|
this.status = options && options.status || 0;
|
|
263
290
|
this.code = options && options.code || 'APICK_API_ERROR';
|
|
264
291
|
this.serviceCode = options && options.serviceCode || undefined;
|
|
292
|
+
if (options && options.details !== undefined) this.details = options.details;
|
|
265
293
|
}
|
|
266
294
|
|
|
267
295
|
toJSON() {
|
|
@@ -524,6 +552,33 @@ class ApickClient {
|
|
|
524
552
|
return this._call('screenshot', { url: normalizeUrl(url) });
|
|
525
553
|
}
|
|
526
554
|
|
|
555
|
+
// 유튜브 공개 영상. url 은 영상 주소(watch·youtu.be·shorts) 또는 11자리 영상 ID를 받는다.
|
|
556
|
+
youtubeMetadata(url) {
|
|
557
|
+
return this._call('youtubeMetadata', { url: requiredString('url', url, 2048) });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
youtubeThumbnail(url) {
|
|
561
|
+
return this._call('youtubeThumbnail', { url: requiredString('url', url, 2048) });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
youtubeSubtitleList(url) {
|
|
565
|
+
return this._call('youtubeSubtitleList', { url: requiredString('url', url, 2048) });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
youtubeSubtitle(url, lang, options) {
|
|
569
|
+
const config = options || {};
|
|
570
|
+
const payload = { url: requiredString('url', url, 2048), lang: requiredString('lang', lang, 32) };
|
|
571
|
+
if (config.format !== undefined) {
|
|
572
|
+
if (!['vtt', 'srt', 'txt'].includes(config.format)) throw new RangeError('format must be vtt, srt or txt.');
|
|
573
|
+
payload.format = config.format;
|
|
574
|
+
}
|
|
575
|
+
if (config.type !== undefined) {
|
|
576
|
+
if (!['any', 'manual', 'auto'].includes(config.type)) throw new RangeError('type must be any, manual or auto.');
|
|
577
|
+
payload.type = config.type;
|
|
578
|
+
}
|
|
579
|
+
return this._call('youtubeSubtitle', payload, null, { filename: 'subtitle.' + (payload.format || 'vtt') });
|
|
580
|
+
}
|
|
581
|
+
|
|
527
582
|
async createVideoJob(model, prompt, options) {
|
|
528
583
|
if (!['seedance', 'veo', 'kling'].includes(model)) throw new RangeError('model must be seedance, veo or kling.');
|
|
529
584
|
const config = options || {};
|
|
@@ -792,6 +847,150 @@ class ApickClient {
|
|
|
792
847
|
getHealthCheckup(transactionId) {
|
|
793
848
|
return this._call('requestHealthCheckup', { transactionId: normalizeTransactionId(transactionId) }, null, { endpoint: '/rest/get_health_checkup' });
|
|
794
849
|
}
|
|
850
|
+
|
|
851
|
+
requestCashReceiptDeduction(input) {
|
|
852
|
+
const payload = dataRequestInput(input);
|
|
853
|
+
const incomeYears = optionalRangeInteger('incomeYears', (input || {}).incomeYears, 1, 3);
|
|
854
|
+
if (incomeYears !== undefined) payload.incomeYears = incomeYears;
|
|
855
|
+
return this._call('requestCashReceiptDeduction', payload);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
getCashReceiptDeduction(transactionId) {
|
|
859
|
+
return this._call('requestCashReceiptDeduction', { transactionId: normalizeTransactionId(transactionId) }, null, { endpoint: '/rest/get_cash_receipt_deduction' });
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
requestTaxReturnHistory(input) {
|
|
863
|
+
const payload = dataRequestInput(input);
|
|
864
|
+
const years = optionalRangeInteger('years', (input || {}).years, 1, 10);
|
|
865
|
+
if (years !== undefined) payload.years = years;
|
|
866
|
+
return this._call('requestTaxReturnHistory', payload);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
getTaxReturnHistory(transactionId) {
|
|
870
|
+
return this._call('requestTaxReturnHistory', { transactionId: normalizeTransactionId(transactionId) }, null, { endpoint: '/rest/get_tax_return_history' });
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Skills: 요청·응답이 모두 JSON 이고 data·api 봉투 없이 응답 항목이 최상위에 온다.
|
|
874
|
+
// 오류는 { error: { code, message, details } } 형식이며 code 를 serviceCode 로 돌려준다.
|
|
875
|
+
async _skills(method, path, options) {
|
|
876
|
+
const config = options || {};
|
|
877
|
+
const controller = new AbortController();
|
|
878
|
+
const timeoutMs = this.#timeoutMs || config.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
879
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
880
|
+
const headers = { Accept: 'application/json', Authorization: `Bearer ${this.#apiKey}` };
|
|
881
|
+
const request = { method, headers, signal: controller.signal, redirect: 'error' };
|
|
882
|
+
if (config.idempotencyKey) headers['Idempotency-Key'] = config.idempotencyKey;
|
|
883
|
+
if (method !== 'GET') {
|
|
884
|
+
headers['Content-Type'] = 'application/json';
|
|
885
|
+
request.body = JSON.stringify(config.body || {});
|
|
886
|
+
}
|
|
887
|
+
let url = this.#baseUrl + path;
|
|
888
|
+
const query = new URLSearchParams(config.query || {}).toString();
|
|
889
|
+
if (query) url += '?' + query;
|
|
890
|
+
|
|
891
|
+
let response;
|
|
892
|
+
try {
|
|
893
|
+
response = await this.#fetch(url, request);
|
|
894
|
+
} catch (error) {
|
|
895
|
+
const timedOut = controller.signal.aborted || error && error.name === 'AbortError';
|
|
896
|
+
throw new ApickApiError(
|
|
897
|
+
timedOut ? `APICK request timed out after ${timeoutMs} ms.` : redact(error && error.message || 'APICK network request failed.', this.#apiKey),
|
|
898
|
+
{ code: timedOut ? 'APICK_TIMEOUT' : 'APICK_NETWORK_ERROR' }
|
|
899
|
+
);
|
|
900
|
+
} finally {
|
|
901
|
+
clearTimeout(timer);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const raw = await response.text();
|
|
905
|
+
let body;
|
|
906
|
+
try {
|
|
907
|
+
body = raw ? JSON.parse(raw) : null;
|
|
908
|
+
} catch {
|
|
909
|
+
body = undefined;
|
|
910
|
+
}
|
|
911
|
+
const failure = body && typeof body === 'object' && body.error && typeof body.error === 'object' ? body.error : null;
|
|
912
|
+
if (!response.ok || failure) {
|
|
913
|
+
throw new ApickApiError(failure && typeof failure.message === 'string' && failure.message || publicErrorMessage(body, 'APICK request failed.'), {
|
|
914
|
+
status: response.status,
|
|
915
|
+
code: response.status === 401 ? 'APICK_AUTH_ERROR' : 'APICK_API_ERROR',
|
|
916
|
+
serviceCode: failure && typeof failure.code === 'string' ? failure.code : undefined,
|
|
917
|
+
details: failure && failure.details && typeof failure.details === 'object' ? failure.details : undefined
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
if (!body || typeof body !== 'object') {
|
|
921
|
+
throw new ApickApiError(`APICK returned an unexpected response (HTTP ${response.status}).`, {
|
|
922
|
+
status: response.status,
|
|
923
|
+
code: 'APICK_INVALID_RESPONSE'
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
return Object.freeze({ data: body, meta: responseMeta(null, response.headers) });
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
searchSkills(options) {
|
|
930
|
+
const config = options || {};
|
|
931
|
+
const query = {};
|
|
932
|
+
if (config.query !== undefined && config.query !== null && config.query !== '') query.query = requiredString('query', config.query, 60);
|
|
933
|
+
if (config.category !== undefined && config.category !== null && config.category !== '') {
|
|
934
|
+
const category = requiredString('category', config.category);
|
|
935
|
+
if (!SKILL_CATEGORY_SET.has(category)) throw new RangeError('category must be one of the supported Skill categories.');
|
|
936
|
+
query.category = category;
|
|
937
|
+
}
|
|
938
|
+
if (config.cursor !== undefined && config.cursor !== null && config.cursor !== '') query.cursor = requiredString('cursor', String(config.cursor), 20);
|
|
939
|
+
const limit = optionalRangeInteger('limit', config.limit, 1, 20);
|
|
940
|
+
if (limit !== undefined) query.limit = String(limit);
|
|
941
|
+
return this._skills('GET', '/rest/skills', { query });
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
getSkill(skillId) {
|
|
945
|
+
return this._skills('GET', '/rest/skills/' + normalizeSkillId(skillId));
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
quoteSkill(skillId, input, options) {
|
|
949
|
+
const body = { input: skillInput(input) };
|
|
950
|
+
const version = optionalSkillText('version', (options || {}).version, 16);
|
|
951
|
+
if (version !== undefined) body.version = version;
|
|
952
|
+
return this._skills('POST', '/rest/skills/' + normalizeSkillId(skillId) + '/quotes', { body });
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// 같은 idempotencyKey 로 다시 보내면 새로 실행하지 않고 처음 실행을 돌려준다. 응답을 못 받았을 때는 같은 키로 재시도한다.
|
|
956
|
+
runSkill(skillId, input, options) {
|
|
957
|
+
const config = options || {};
|
|
958
|
+
const idempotencyKey = requiredString('idempotencyKey', config.idempotencyKey);
|
|
959
|
+
if (!/^[A-Za-z0-9._:-]{1,128}$/.test(idempotencyKey)) {
|
|
960
|
+
throw new TypeError('idempotencyKey must be 1-128 characters of letters, digits, and . _ : -');
|
|
961
|
+
}
|
|
962
|
+
const body = { input: skillInput(input) };
|
|
963
|
+
const version = optionalSkillText('version', config.version, 16);
|
|
964
|
+
const quoteId = optionalSkillText('quoteId', config.quoteId, 40);
|
|
965
|
+
const maxCostPoints = config.maxCostPoints === undefined || config.maxCostPoints === null ? undefined : positiveInteger('maxCostPoints', config.maxCostPoints);
|
|
966
|
+
const waitSeconds = optionalRangeInteger('waitSeconds', config.waitSeconds, 0, 20);
|
|
967
|
+
if (version !== undefined) body.version = version;
|
|
968
|
+
if (quoteId !== undefined) body.quote_id = quoteId;
|
|
969
|
+
if (maxCostPoints !== undefined) body.max_cost_points = maxCostPoints;
|
|
970
|
+
if (waitSeconds !== undefined) body.wait_seconds = waitSeconds;
|
|
971
|
+
return this._skills('POST', '/rest/skills/' + normalizeSkillId(skillId) + '/runs', { body, idempotencyKey, timeoutMs: 40_000 });
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
getSkillRun(runId) {
|
|
975
|
+
return this._skills('GET', '/rest/skills/runs/' + normalizeSkillRunId(runId));
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
getSkillRunResult(runId) {
|
|
979
|
+
return this._skills('GET', '/rest/skills/runs/' + normalizeSkillRunId(runId) + '/result');
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
cancelSkillRun(runId) {
|
|
983
|
+
return this._skills('POST', '/rest/skills/runs/' + normalizeSkillRunId(runId) + '/cancel', { body: {} });
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
skillUsage(options) {
|
|
987
|
+
const config = options || {};
|
|
988
|
+
const query = {};
|
|
989
|
+
if (config.cursor !== undefined && config.cursor !== null && config.cursor !== '') query.cursor = requiredString('cursor', String(config.cursor), 20);
|
|
990
|
+
const limit = optionalRangeInteger('limit', config.limit, 1, 50);
|
|
991
|
+
if (limit !== undefined) query.limit = String(limit);
|
|
992
|
+
return this._skills('GET', '/rest/skills/usage', { query });
|
|
993
|
+
}
|
|
795
994
|
}
|
|
796
995
|
|
|
797
996
|
module.exports = {
|
|
@@ -801,5 +1000,6 @@ module.exports = {
|
|
|
801
1000
|
SERVICES,
|
|
802
1001
|
TTS_VOICE_IDS,
|
|
803
1002
|
AUTH_PROVIDERS,
|
|
1003
|
+
SKILL_CATEGORIES,
|
|
804
1004
|
DEFAULT_BASE_URL
|
|
805
1005
|
};
|
package/src/index.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ export interface RequestPersonalIncomeInput extends DataRequestInput { incomeYea
|
|
|
61
61
|
export interface RequestNpsJoinHistoryInput extends DataRequestInput { from?: string; to?: string; }
|
|
62
62
|
export interface RequestDrivingLicenseInput extends DataRequestInput {}
|
|
63
63
|
export interface RequestHealthCheckupInput extends DataRequestInput {}
|
|
64
|
+
export interface RequestCashReceiptDeductionInput extends DataRequestInput { incomeYears?: number; }
|
|
65
|
+
export interface RequestTaxReturnHistoryInput extends DataRequestInput { years?: number; }
|
|
64
66
|
|
|
65
67
|
export type DataRequestStatus =
|
|
66
68
|
| 'AUTH_REQUESTED' | 'AUTH_WAITING' | 'AUTH_COMPLETED' | 'AUTH_REJECTED' | 'AUTH_EXPIRED'
|
|
@@ -159,6 +161,44 @@ export interface HealthCheckupEntry { 검진연도: string; 검진종류: string
|
|
|
159
161
|
export interface HealthCheckupInfo { 이름: string; 건수: number; 검진내역: HealthCheckupEntry[]; }
|
|
160
162
|
export interface HealthCheckupResultPayload { healthCheckup: HealthCheckupInfo; }
|
|
161
163
|
|
|
164
|
+
export interface CashReceiptTotals { 건수: number; 사용금액: number; 소득공제건수: number; 소득공제금액: number; }
|
|
165
|
+
export interface CashReceiptEntry {
|
|
166
|
+
거래일시: string; 가맹점: string; 금액: number; 승인번호: string;
|
|
167
|
+
거래구분: string; 거래상태: string; 소득공제대상: boolean; 소득공제반영: boolean;
|
|
168
|
+
}
|
|
169
|
+
export interface CashReceiptYear { 귀속연도: string; 합계: CashReceiptTotals; 사용내역: CashReceiptEntry[]; }
|
|
170
|
+
export interface CashReceiptDeductionInfo { 조회연도: string[]; 전체합계: CashReceiptTotals; 연도별: CashReceiptYear[]; }
|
|
171
|
+
export interface CashReceiptDeductionResultPayload { cashReceiptDeduction: CashReceiptDeductionInfo; }
|
|
172
|
+
|
|
173
|
+
export interface TaxReturnEntry {
|
|
174
|
+
신고일: string; 과세기간: string; 신고서: string; 신고구분: string; 신고상세: string;
|
|
175
|
+
세목: string; 작성방법: string; 납부년월: string; 납부금액: number; 고지금액: number;
|
|
176
|
+
}
|
|
177
|
+
export interface TaxReturnHistoryInfo {
|
|
178
|
+
조회기간: { 시작: string; 끝: string };
|
|
179
|
+
합계: { 건수: number; 납부금액: number; 고지금액: number };
|
|
180
|
+
신고내역: TaxReturnEntry[];
|
|
181
|
+
}
|
|
182
|
+
export interface TaxReturnHistoryResultPayload { taxReturnHistory: TaxReturnHistoryInfo; }
|
|
183
|
+
|
|
184
|
+
export interface YoutubeThumbnail { url: string; width: number; height: number; }
|
|
185
|
+
export interface YoutubeChapter { title: string; start_time: number | null; end_time: number | null; }
|
|
186
|
+
export interface YoutubeMetadata {
|
|
187
|
+
video_id: string; url: string; title: string | null; description: string;
|
|
188
|
+
channel: { id: string | null; name: string | null; url: string | null; handle: string | null; follower_count: number | null; is_verified: boolean };
|
|
189
|
+
upload_date: string | null; duration: number | null; view_count: number | null; like_count: number | null; comment_count: number | null;
|
|
190
|
+
categories: string[]; tags: string[]; language: string | null; live_status: string | null; availability: string | null;
|
|
191
|
+
age_limit: number | null; chapters: YoutubeChapter[]; thumbnail: string | null; thumbnails: YoutubeThumbnail[];
|
|
192
|
+
subtitle_languages: string[]; automatic_caption_count: number;
|
|
193
|
+
}
|
|
194
|
+
export interface YoutubeSubtitleTrack { lang: string; name: string | null; auto: boolean; formats: string[]; translated?: boolean; }
|
|
195
|
+
export interface YoutubeSubtitleList {
|
|
196
|
+
video_id: string; title: string | null; original_language: string | null;
|
|
197
|
+
subtitle_count: number; automatic_caption_count: number;
|
|
198
|
+
subtitles: YoutubeSubtitleTrack[]; automatic_captions: YoutubeSubtitleTrack[];
|
|
199
|
+
}
|
|
200
|
+
export interface YoutubeSubtitleOptions { format?: 'vtt' | 'srt' | 'txt'; type?: 'any' | 'manual' | 'auto'; }
|
|
201
|
+
|
|
162
202
|
export const TTS_VOICE_IDS: readonly [
|
|
163
203
|
'v2_ann_m_30s_01', 'v2_ann_m_30s_02', 'v2_ann_m_30s_04', 'v2_ann_m_30s_05', 'v2_ann_f_30s_01', 'v2_ann_f_30s_02', 'v2_ann_f_30s_03', 'v2_ann_f_30s_04', 'v2_ann_f_30s_05', 'v2_m_teen_01', 'v2_m_young_01', 'v2_m_mid_01', 'v2_m_senior_01', 'v2_f_teen_01', 'v2_f_young_01', 'v2_f_senior_01'
|
|
164
204
|
];
|
|
@@ -192,6 +232,8 @@ export class ApickApiError extends Error {
|
|
|
192
232
|
readonly status: number;
|
|
193
233
|
readonly code: string;
|
|
194
234
|
readonly serviceCode?: string;
|
|
235
|
+
/** Skills 오류의 부가 정보. 예: INVALID_INPUT 의 `errors` 목록 */
|
|
236
|
+
readonly details?: Record<string, unknown>;
|
|
195
237
|
toJSON(): {
|
|
196
238
|
name: string;
|
|
197
239
|
message: string;
|
|
@@ -244,6 +286,10 @@ export class ApickClient {
|
|
|
244
286
|
googleSearch(keyword: string, options?: { page?: number }): Promise<ApickResult>;
|
|
245
287
|
googleImageSearch(keyword: string, options?: { page?: number }): Promise<ApickResult>;
|
|
246
288
|
screenshot(url: string): Promise<ApickBinaryResult>;
|
|
289
|
+
youtubeMetadata(url: string): Promise<ApickResult<YoutubeMetadata>>;
|
|
290
|
+
youtubeThumbnail(url: string): Promise<ApickBinaryResult>;
|
|
291
|
+
youtubeSubtitleList(url: string): Promise<ApickResult<YoutubeSubtitleList>>;
|
|
292
|
+
youtubeSubtitle(url: string, lang: string, options?: YoutubeSubtitleOptions): Promise<ApickBinaryResult>;
|
|
247
293
|
createTtsJob(text: string, options?: { voiceId?: TtsVoiceId }): Promise<ApickResult<TtsJobData>>;
|
|
248
294
|
getTtsJob(jobId: string): Promise<ApickResult<TtsJobData>>;
|
|
249
295
|
cancelTtsJob(jobId: string): Promise<ApickResult<TtsJobData>>;
|
|
@@ -273,6 +319,82 @@ export class ApickClient {
|
|
|
273
319
|
getDrivingLicense(transactionId: string): Promise<ApickResult<DataRequestResult<DrivingLicenseResultPayload>>>;
|
|
274
320
|
requestHealthCheckup(input: RequestHealthCheckupInput): Promise<ApickResult<DataRequestAcceptedData>>;
|
|
275
321
|
getHealthCheckup(transactionId: string): Promise<ApickResult<DataRequestResult<HealthCheckupResultPayload>>>;
|
|
322
|
+
requestCashReceiptDeduction(input: RequestCashReceiptDeductionInput): Promise<ApickResult<DataRequestAcceptedData>>;
|
|
323
|
+
getCashReceiptDeduction(transactionId: string): Promise<ApickResult<DataRequestResult<CashReceiptDeductionResultPayload>>>;
|
|
324
|
+
requestTaxReturnHistory(input: RequestTaxReturnHistoryInput): Promise<ApickResult<DataRequestAcceptedData>>;
|
|
325
|
+
getTaxReturnHistory(transactionId: string): Promise<ApickResult<DataRequestResult<TaxReturnHistoryResultPayload>>>;
|
|
326
|
+
searchSkills(options?: SkillSearchOptions): Promise<ApickResult<SkillPage<SkillSummary>>>;
|
|
327
|
+
getSkill(skillId: string): Promise<ApickResult<SkillDetail>>;
|
|
328
|
+
quoteSkill(skillId: string, input: Record<string, unknown>, options?: { version?: string }): Promise<ApickResult<SkillQuote>>;
|
|
329
|
+
runSkill<T = Record<string, unknown>>(skillId: string, input: Record<string, unknown>, options: SkillRunOptions): Promise<ApickResult<SkillRun<T>>>;
|
|
330
|
+
getSkillRun<T = Record<string, unknown>>(runId: string): Promise<ApickResult<SkillRun<T>>>;
|
|
331
|
+
getSkillRunResult<T = Record<string, unknown>>(runId: string): Promise<ApickResult<SkillRunResult<T>>>;
|
|
332
|
+
cancelSkillRun(runId: string): Promise<ApickResult<SkillRun>>;
|
|
333
|
+
skillUsage(options?: { cursor?: string; limit?: number }): Promise<ApickResult<SkillPage<SkillRun>>>;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export const SKILL_CATEGORIES: readonly ['data', 'ai', 'dev', 'document', 'marketing', 'finance', 'productivity', 'video', 'etc'];
|
|
337
|
+
export type SkillCategory = typeof SKILL_CATEGORIES[number];
|
|
338
|
+
export type SkillRunStatus = 'queued' | 'running' | 'completing' | 'succeeded' | 'failed' | 'timed_out' | 'cancelled';
|
|
339
|
+
export type SkillBillingStatus = 'reserved' | 'captured' | 'released' | 'partially_refunded' | 'refunded';
|
|
340
|
+
export type SkillFailureCode = 'EXECUTION_FAILED' | 'OUTPUT_INVALID' | 'TIMED_OUT' | 'CANCELLED' | 'UPSTREAM_UNAVAILABLE';
|
|
341
|
+
/** `ApickApiError.serviceCode` 로 돌아오는 Skills 오류 코드 */
|
|
342
|
+
export type SkillErrorCode =
|
|
343
|
+
| 'INVALID_INPUT' | 'AUTH_REQUIRED' | 'NOT_ALLOWED' | 'PAYMENT_REQUIRED' | 'SKILL_OR_RUN_NOT_FOUND'
|
|
344
|
+
| 'IDEMPOTENCY_KEY_REQUIRED' | 'IDEMPOTENCY_CONFLICT' | 'QUOTE_EXPIRED' | 'VERSION_UNAVAILABLE'
|
|
345
|
+
| 'PRICE_EXCEEDS_LIMIT' | 'RUN_NOT_CANCELLABLE' | 'RESULT_NOT_READY' | 'RESULT_EXPIRED'
|
|
346
|
+
| 'INSUFFICIENT_POINTS' | 'RATE_LIMITED' | 'BUDGET_EXCEEDED' | 'TEMPORARILY_UNAVAILABLE';
|
|
347
|
+
export interface SkillSearchOptions { query?: string; category?: SkillCategory; cursor?: string; limit?: number; }
|
|
348
|
+
export interface SkillRunOptions {
|
|
349
|
+
/** 이 실행을 구분하는 고유 값. 재시도할 때 같은 값을 씁니다. 영문·숫자와 `. _ : -` 1~128자 */
|
|
350
|
+
idempotencyKey: string;
|
|
351
|
+
version?: string;
|
|
352
|
+
quoteId?: string;
|
|
353
|
+
/** 가격이 이 값보다 높으면 실행하지 않습니다 */
|
|
354
|
+
maxCostPoints?: number;
|
|
355
|
+
/** 결과를 기다릴 시간(0~20초, 기본 20) */
|
|
356
|
+
waitSeconds?: number;
|
|
357
|
+
}
|
|
358
|
+
export interface SkillPage<T> { items: T[]; next_cursor: string | null; }
|
|
359
|
+
export interface SkillLimits { max_input_chars: number; timeout_seconds: number; }
|
|
360
|
+
export interface SkillSummary {
|
|
361
|
+
skill_id: string; slug: string; title: string; summary: string;
|
|
362
|
+
category: SkillCategory; category_label: string; version: string;
|
|
363
|
+
/** 실행마다 같은 기본 금액 / base amount charged on every run */
|
|
364
|
+
price_points: number;
|
|
365
|
+
/** 사용량까지 더한 예상 금액 / estimated amount including usage */
|
|
366
|
+
estimated_points: number;
|
|
367
|
+
/** true 면 실행마다 실제 사용량만큼 금액이 달라집니다 / amount varies per run with actual usage */
|
|
368
|
+
usage_priced: boolean;
|
|
369
|
+
seller: { name: string }; uses_generative_ai: boolean;
|
|
370
|
+
}
|
|
371
|
+
export interface SkillDetail extends SkillSummary {
|
|
372
|
+
description: string; billing_rule: 'validated_result'; limits: SkillLimits;
|
|
373
|
+
input_schema: Record<string, unknown>; output_schema: Record<string, unknown>;
|
|
374
|
+
examples: unknown[]; stats: Record<string, unknown> | null; published_at: string | null;
|
|
375
|
+
}
|
|
376
|
+
export interface SkillQuote {
|
|
377
|
+
quote_id: string; skill_id: string; version: string; price_points: number;
|
|
378
|
+
/** 이 입력의 예상 금액 / estimated amount for this input */
|
|
379
|
+
estimated_points: number;
|
|
380
|
+
/** 예약되는 최대 금액. 실제 차감액은 이 값을 넘지 않습니다 / maximum amount reserved; the charge never exceeds it */
|
|
381
|
+
max_points: number;
|
|
382
|
+
usage_priced: boolean;
|
|
383
|
+
expires_at: string; limits: SkillLimits; billing_rule: 'validated_result';
|
|
384
|
+
}
|
|
385
|
+
export interface SkillBilling { status: SkillBillingStatus; reserved_points: number; charged_points: number; refunded_points: number; }
|
|
386
|
+
export interface SkillRun<T = Record<string, unknown>> {
|
|
387
|
+
run_id: string; status: SkillRunStatus;
|
|
388
|
+
skill: { id: string; version: string; title?: string };
|
|
389
|
+
billing: SkillBilling; created_at: string | null; completed_at: string | null;
|
|
390
|
+
links: { self: string; result?: string };
|
|
391
|
+
failure_code?: SkillFailureCode; result_expires_at?: string | null;
|
|
392
|
+
/** status 가 succeeded 일 때만 */
|
|
393
|
+
result?: T;
|
|
394
|
+
}
|
|
395
|
+
export interface SkillRunResult<T = Record<string, unknown>> {
|
|
396
|
+
run_id: string; skill: { id: string; version: string; title?: string };
|
|
397
|
+
result: T; result_expires_at: string | null;
|
|
276
398
|
}
|
|
277
399
|
|
|
278
400
|
export default ApickClient;
|