apick-api 4.0.1 → 4.1.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 +10 -0
- package/LICENSE +21 -21
- package/README.md +16 -1
- package/SECURITY.md +11 -11
- package/docs/guide.en.md +24 -1
- package/docs/guide.ko.md +24 -1
- package/docs/skill-artifacts.md +24 -24
- package/docs/subagent.md +39 -39
- package/docs/tts.md +34 -34
- package/examples/basic.mjs +10 -10
- package/examples/commonjs.cjs +12 -12
- package/examples/files.mjs +9 -9
- package/package.json +1 -1
- package/src/form.cjs +28 -28
- package/src/index.cjs +1251 -1091
- package/src/index.d.ts +705 -533
- package/src/index.js +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# 4.1.0
|
|
2
|
+
|
|
3
|
+
- Add YouTube methods: `youtubeSearch`, `youtubeChannel`, `youtubePlaylist`, `youtubeHashtag`, `youtubeFormats`, `youtubeComments`, `downloadYoutubeVideo` and `downloadYoutubeAudio`. Downloads return a download link valid for 1 hour and are billed as a base fee plus 2 points per 10MB.
|
|
4
|
+
- 유튜브 검색·채널·재생목록·해시태그·다운로드 화질·댓글 조회와 영상(MP4)·오디오(MP3·M4A·Opus) 다운로드 메서드를 추가했습니다. 다운로드는 1시간 유효한 링크를 돌려주며 기본요금에 파일 10MB당 2포인트가 더해집니다.
|
|
5
|
+
- Add `googleNewsSearch`, `googleShoppingSearch`, `googleMapsSearch`, `googleRankCheck`, `instagramProfile`, `instagramPost` and `tiktokProfile` with typed results. Failed calls are not charged.
|
|
6
|
+
- `googleRankCheck` returns `complete`, `checked_pages` and `unchecked_ranks`; when some rank ranges could not be checked, only the checked share is charged.
|
|
7
|
+
- `googleSearch` returns up to 10 results per page; `googleImageSearch` pages are 1-5 (20 per page). Search timeouts are 50 seconds.
|
|
8
|
+
- 구글 뉴스·쇼핑·지도 장소 검색, 검색 순위 확인(1~100위), 인스타그램 프로필·게시물/릴스, 틱톡 프로필 메서드와 결과 타입을 추가했습니다. 실패한 호출은 과금되지 않습니다.
|
|
9
|
+
- `googleSearch` 는 페이지당 최대 10건, `googleImageSearch` 의 page 는 1~5(페이지당 20건)입니다.
|
|
10
|
+
|
|
1
11
|
# 4.0.1 — 2026-10-06
|
|
2
12
|
|
|
3
13
|
- 이미지 생성·편집에 `quality` 옵션을 추가했습니다: `basic`(기본) 40P · `advanced` 350P · `premium` 1,400P(장당 고정가, 크기 무관).
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 APICK
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 APICK
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -69,12 +69,27 @@ Leave the allowed-IP list blank for unrestricted access. To restrict access, reg
|
|
|
69
69
|
| `geolocate(address)` | 도메인·IP 위치 / Domain and IP location | JSON |
|
|
70
70
|
| `whois(address)` | WHOIS 조회 / WHOIS lookup | JSON |
|
|
71
71
|
| `googleSearch(keyword, options)` | 웹 검색 / Web search | JSON |
|
|
72
|
-
| `googleImageSearch(keyword, options)` | 이미지 검색 / Image search | JSON |
|
|
72
|
+
| `googleImageSearch(keyword, options)` | 이미지 검색(page 1~5) / Image search (pages 1-5) | JSON |
|
|
73
|
+
| `googleNewsSearch(keyword, options)` | 구글 뉴스 검색 / Google News search | JSON |
|
|
74
|
+
| `googleShoppingSearch(keyword, options)` | 구글 쇼핑 검색 / Google Shopping search | JSON |
|
|
75
|
+
| `googleMapsSearch(keyword)` | 구글 지도 장소 검색 / Google Maps place search | JSON |
|
|
76
|
+
| `googleRankCheck(keyword, domain)` | 구글 검색 순위 확인(1~100위) / Google rank check (top 100) | JSON |
|
|
73
77
|
| `screenshot(url)` | 웹페이지 화면캡처 / Web screenshot | Binary |
|
|
74
78
|
| `youtubeMetadata(url)` | 유튜브 영상 정보 / YouTube video metadata | JSON |
|
|
75
79
|
| `youtubeThumbnail(url)` | 유튜브 썸네일 / YouTube thumbnail | JPG |
|
|
76
80
|
| `youtubeSubtitleList(url)` | 유튜브 자막 언어 목록 / YouTube subtitle languages | JSON |
|
|
77
81
|
| `youtubeSubtitle(url, lang, options)` | 유튜브 자막 다운로드 / YouTube subtitles | VTT·SRT·TXT |
|
|
82
|
+
| `youtubeSearch(query, options)` | 유튜브 검색(정렬·필터) / YouTube search with sort and filters | JSON |
|
|
83
|
+
| `youtubeChannel(channel, options)` | 유튜브 채널 정보·탭 목록 / YouTube channel and tab items | JSON |
|
|
84
|
+
| `youtubePlaylist(url, options)` | 유튜브 재생목록 / YouTube playlist | JSON |
|
|
85
|
+
| `youtubeHashtag(hashtag, options)` | 유튜브 해시태그 영상 / YouTube hashtag videos | JSON |
|
|
86
|
+
| `youtubeFormats(url)` | 다운로드 화질·예상 요금 / Downloadable formats and estimated cost | JSON |
|
|
87
|
+
| `youtubeComments(url, options)` | 유튜브 댓글 / YouTube comments | JSON |
|
|
88
|
+
| `downloadYoutubeVideo(url, options)` | 유튜브 영상 MP4 다운로드 링크(1시간) / YouTube MP4 download link (1 hour) | JSON |
|
|
89
|
+
| `downloadYoutubeAudio(url, options)` | 유튜브 오디오 MP3·M4A·Opus 다운로드 링크(1시간) / YouTube audio download link (1 hour) | JSON |
|
|
90
|
+
| `instagramProfile(usernameOrUrl)` | 인스타그램 공개 프로필 / Instagram public profile | JSON |
|
|
91
|
+
| `instagramPost(url)` | 인스타그램 게시물·릴스 / Instagram post or reel | JSON |
|
|
92
|
+
| `tiktokProfile(usernameOrUrl)` | 틱톡 공개 프로필 / TikTok public profile | JSON |
|
|
78
93
|
| `createTtsJob(text, options)` | 한국어 내레이션 작업 접수 / Create TTS job | JSON |
|
|
79
94
|
| `getTtsJob(jobId)` | TTS 작업 상태 / TTS job status | JSON |
|
|
80
95
|
| `cancelTtsJob(jobId)` | 대기·생성 중 TTS 작업 취소 / Cancel waiting or processing TTS job | JSON |
|
package/SECURITY.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Security
|
|
2
|
-
|
|
3
|
-
## API keys
|
|
4
|
-
|
|
5
|
-
Keep APICK API keys in server-side environment variables. Do not commit keys, include them in client-side browser bundles, place them in URLs, or print them in logs.
|
|
6
|
-
|
|
7
|
-
If a key may have been exposed, regenerate it from your APICK account immediately.
|
|
8
|
-
|
|
9
|
-
## Reporting a vulnerability
|
|
10
|
-
|
|
11
|
-
Please report security issues privately through the contact channel listed at <https://apick.app>. Do not open a public issue containing credentials, personal data, or exploit details.
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## API keys
|
|
4
|
+
|
|
5
|
+
Keep APICK API keys in server-side environment variables. Do not commit keys, include them in client-side browser bundles, place them in URLs, or print them in logs.
|
|
6
|
+
|
|
7
|
+
If a key may have been exposed, regenerate it from your APICK account immediately.
|
|
8
|
+
|
|
9
|
+
## Reporting a vulnerability
|
|
10
|
+
|
|
11
|
+
Please report security issues privately through the contact channel listed at <https://apick.app>. Do not open a public issue containing credentials, personal data, or exploit details.
|
package/docs/guide.en.md
CHANGED
|
@@ -67,7 +67,21 @@ const dns = await client.dnsLookup('apick.app');
|
|
|
67
67
|
const location = await client.geolocate('apick.app');
|
|
68
68
|
const registration = await client.whois('apick.app');
|
|
69
69
|
const web = await client.googleSearch('APICK API', { page: 1 });
|
|
70
|
-
const images = await client.googleImageSearch('Seoul skyline', { page: 1 });
|
|
70
|
+
const images = await client.googleImageSearch('Seoul skyline', { page: 1 }); // pages 1-5
|
|
71
|
+
const news = await client.googleNewsSearch('semiconductor', { page: 1 });
|
|
72
|
+
const shopping = await client.googleShoppingSearch('wireless earbuds');
|
|
73
|
+
const places = await client.googleMapsSearch('Gangnam cafe'); // usually 30-60 seconds
|
|
74
|
+
const rank = await client.googleRankCheck('address search API', 'apick.app'); // top 100
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Public social profiles
|
|
78
|
+
|
|
79
|
+
Only public information from public accounts is returned. Lookups usually take 20-60 seconds, so allow a request timeout of at least 100 seconds. Failed calls are not charged.
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const instagram = await client.instagramProfile('natgeo');
|
|
83
|
+
const post = await client.instagramPost('https://www.instagram.com/p/DeHufcWDCjf/');
|
|
84
|
+
const tiktok = await client.tiktokProfile('https://www.tiktok.com/@tiktok');
|
|
71
85
|
```
|
|
72
86
|
|
|
73
87
|
## OCR
|
|
@@ -106,6 +120,15 @@ const subtitle = await client.youtubeSubtitle(video.data.video_id, 'en', { forma
|
|
|
106
120
|
await subtitle.save('./' + subtitle.filename);
|
|
107
121
|
await (await client.youtubeThumbnail(video.data.video_id)).save('./thumbnail.jpg');
|
|
108
122
|
|
|
123
|
+
// Search, channel, comments; check formats, then download (base fee + per-10MB fee, 1-hour link)
|
|
124
|
+
const found = await client.youtubeSearch('파이썬 기초 강좌', { count: 5, sort: 'views' });
|
|
125
|
+
const channel = await client.youtubeChannel('@jocoding', { tab: 'videos', count: 10 });
|
|
126
|
+
const comments = await client.youtubeComments(found.data.results[0].id, { count: 20, sort: 'top' });
|
|
127
|
+
const formats = await client.youtubeFormats(found.data.results[0].id);
|
|
128
|
+
const mp4 = await client.downloadYoutubeVideo(found.data.results[0].id, { quality: '720' });
|
|
129
|
+
console.log(mp4.data.download_url, mp4.data.billing.total);
|
|
130
|
+
const mp3 = await client.downloadYoutubeAudio(found.data.results[0].id, { format: 'mp3', bitrate: 192, start: '0:30', end: '1:30' });
|
|
131
|
+
|
|
109
132
|
const created = await client.createTtsJob('오늘의 이야기를 시작합니다.', { voiceId: 'Kore' });
|
|
110
133
|
const jobId = created.data.job_id;
|
|
111
134
|
let job = await client.getTtsJob(jobId);
|
package/docs/guide.ko.md
CHANGED
|
@@ -67,7 +67,21 @@ const dns = await client.dnsLookup('apick.app');
|
|
|
67
67
|
const location = await client.geolocate('apick.app');
|
|
68
68
|
const registration = await client.whois('apick.app');
|
|
69
69
|
const web = await client.googleSearch('에이픽 API', { page: 1 });
|
|
70
|
-
const images = await client.googleImageSearch('서울 야경', { page: 1 });
|
|
70
|
+
const images = await client.googleImageSearch('서울 야경', { page: 1 }); // page 1~5
|
|
71
|
+
const news = await client.googleNewsSearch('반도체', { page: 1 });
|
|
72
|
+
const shopping = await client.googleShoppingSearch('무선 이어폰');
|
|
73
|
+
const places = await client.googleMapsSearch('강남역 카페'); // 보통 30~60초
|
|
74
|
+
const rank = await client.googleRankCheck('주소 검색 API', 'apick.app'); // 1~100위
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## SNS 공개 정보
|
|
78
|
+
|
|
79
|
+
공개 계정의 공개 정보만 제공합니다. 처리에 보통 20~60초가 걸리므로 요청 시간 제한을 100초 이상으로 두세요. 실패한 호출은 과금되지 않습니다.
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const instagram = await client.instagramProfile('natgeo');
|
|
83
|
+
const post = await client.instagramPost('https://www.instagram.com/p/DeHufcWDCjf/');
|
|
84
|
+
const tiktok = await client.tiktokProfile('https://www.tiktok.com/@tiktok');
|
|
71
85
|
```
|
|
72
86
|
|
|
73
87
|
## OCR
|
|
@@ -106,6 +120,15 @@ const subtitle = await client.youtubeSubtitle(video.data.video_id, 'en', { forma
|
|
|
106
120
|
await subtitle.save('./' + subtitle.filename);
|
|
107
121
|
await (await client.youtubeThumbnail(video.data.video_id)).save('./thumbnail.jpg');
|
|
108
122
|
|
|
123
|
+
// 검색·채널·댓글, 화질 확인 후 다운로드(기본요금 + 10MB당 요금, 1시간 유효 링크)
|
|
124
|
+
const found = await client.youtubeSearch('파이썬 기초 강좌', { count: 5, sort: 'views' });
|
|
125
|
+
const channel = await client.youtubeChannel('@jocoding', { tab: 'videos', count: 10 });
|
|
126
|
+
const comments = await client.youtubeComments(found.data.results[0].id, { count: 20, sort: 'top' });
|
|
127
|
+
const formats = await client.youtubeFormats(found.data.results[0].id);
|
|
128
|
+
const mp4 = await client.downloadYoutubeVideo(found.data.results[0].id, { quality: '720' });
|
|
129
|
+
console.log(mp4.data.download_url, mp4.data.billing.total);
|
|
130
|
+
const mp3 = await client.downloadYoutubeAudio(found.data.results[0].id, { format: 'mp3', bitrate: 192, start: '0:30', end: '1:30' });
|
|
131
|
+
|
|
109
132
|
const created = await client.createTtsJob('오늘의 이야기를 시작합니다.', { voiceId: 'Kore' });
|
|
110
133
|
const jobId = created.data.job_id;
|
|
111
134
|
let job = await client.getTtsJob(jobId);
|
package/docs/skill-artifacts.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# 스킬 결과 파일 · Skill artifacts
|
|
2
|
-
|
|
3
|
-
스킬 상세와 견적의 `execution_info`는 예상 가격 범위, 처리 시간 범위, 도구별 보통·최대 호출 수를 제공합니다. 실측 표본에 따른 예상이며 입력에 따라 달라집니다. 실행 전 견적의 `max_points`를 확인하세요.
|
|
4
|
-
|
|
5
|
-
`execution_info` on Skill details and quotes describes estimated price and duration ranges and typical/maximum tool calls. These are estimates, not guarantees. Check `max_points` before running a Skill.
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
const quote = await apick.quoteSkill(skillId, input);
|
|
9
|
-
console.log(quote.data.execution_info);
|
|
10
|
-
const run = await apick.runSkill(skillId, input, {
|
|
11
|
-
idempotencyKey: 'my-unique-request',
|
|
12
|
-
quoteId: quote.data.quote_id,
|
|
13
|
-
maxCostPoints: quote.data.max_points
|
|
14
|
-
});
|
|
15
|
-
// Poll getSkillRun until status is succeeded, then:
|
|
16
|
-
const result = await apick.getSkillRunResult(run.data.run_id);
|
|
17
|
-
const file = result.data.result.artifacts[0];
|
|
18
|
-
const downloaded = await apick.getSkillArtifact(run.data.run_id, file.id);
|
|
19
|
-
await downloaded.save('./result.png');
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
파일의 `name`, `mime_type`, `bytes`, `sha256`을 확인하고 실제 형식에 맞는 확장자로 저장하세요. 인증키 소유자의 실행 파일만 내려받을 수 있고, 실행 결과가 만료되면 파일도 만료됩니다. 실패·취소·시간 초과는 예약액을 반환하며, 가변 요금 상품은 성공 시 실제 사용료를 정산합니다. 공급자 청구서와 대조한 확정 원가를 뜻하지 않습니다.
|
|
23
|
-
|
|
24
|
-
Use `name`, `mime_type`, `bytes`, and `sha256` to identify and verify each file; choose a matching extension. Downloads require the run owner's API key and expire with the result. Failed, cancelled, and timed-out runs release the reservation. Variable-price Skills settle recorded tool usage on success; calculated vendor costs are not invoice-reconciled amounts.
|
|
1
|
+
# 스킬 결과 파일 · Skill artifacts
|
|
2
|
+
|
|
3
|
+
스킬 상세와 견적의 `execution_info`는 예상 가격 범위, 처리 시간 범위, 도구별 보통·최대 호출 수를 제공합니다. 실측 표본에 따른 예상이며 입력에 따라 달라집니다. 실행 전 견적의 `max_points`를 확인하세요.
|
|
4
|
+
|
|
5
|
+
`execution_info` on Skill details and quotes describes estimated price and duration ranges and typical/maximum tool calls. These are estimates, not guarantees. Check `max_points` before running a Skill.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const quote = await apick.quoteSkill(skillId, input);
|
|
9
|
+
console.log(quote.data.execution_info);
|
|
10
|
+
const run = await apick.runSkill(skillId, input, {
|
|
11
|
+
idempotencyKey: 'my-unique-request',
|
|
12
|
+
quoteId: quote.data.quote_id,
|
|
13
|
+
maxCostPoints: quote.data.max_points
|
|
14
|
+
});
|
|
15
|
+
// Poll getSkillRun until status is succeeded, then:
|
|
16
|
+
const result = await apick.getSkillRunResult(run.data.run_id);
|
|
17
|
+
const file = result.data.result.artifacts[0];
|
|
18
|
+
const downloaded = await apick.getSkillArtifact(run.data.run_id, file.id);
|
|
19
|
+
await downloaded.save('./result.png');
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
파일의 `name`, `mime_type`, `bytes`, `sha256`을 확인하고 실제 형식에 맞는 확장자로 저장하세요. 인증키 소유자의 실행 파일만 내려받을 수 있고, 실행 결과가 만료되면 파일도 만료됩니다. 실패·취소·시간 초과는 예약액을 반환하며, 가변 요금 상품은 성공 시 실제 사용료를 정산합니다. 공급자 청구서와 대조한 확정 원가를 뜻하지 않습니다.
|
|
23
|
+
|
|
24
|
+
Use `name`, `mime_type`, `bytes`, and `sha256` to identify and verify each file; choose a matching extension. Downloads require the run owner's API key and expire with the result. Failed, cancelled, and timed-out runs release the reservation. Variable-price Skills settle recorded tool usage on success; calculated vendor costs are not invoice-reconciled amounts.
|
package/docs/subagent.md
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
# 서브에이전트 / Subagent
|
|
2
|
-
|
|
3
|
-
`apick-agent`는 설치형 상품이며 `quoteSkill`·`runSkill` 대신 전용 API를 사용합니다. 성공 작업의 확인된 원가에 40%를 가산하며 승인된 동일 결과 캐시는 무료입니다. 충전 잔액 외 상품 사용량 한도는 없습니다.
|
|
4
|
-
|
|
5
|
-
`apick-agent` is an installed-agent product. Use the dedicated API rather than `quoteSkill` or `runSkill`. Successful work is billed at confirmed model cost plus 40%; approved identical-result cache reuse is free. There are no product usage quotas beyond prepaid balance.
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
import { ApickClient } from 'apick-api';
|
|
9
|
-
import { createHash } from 'node:crypto';
|
|
10
|
-
const api = new ApickClient({ apiKey: process.env.APICK_API_KEY });
|
|
11
|
-
const bytes = Buffer.from('검토할 공개 문서입니다.');
|
|
12
|
-
const file = await api.createSubagentFile({
|
|
13
|
-
path: 'docs/sample.txt', bytes: bytes.length,
|
|
14
|
-
sha256: createHash('sha256').update(bytes).digest('hex'), retention: 'seven_days'
|
|
15
|
-
});
|
|
16
|
-
if (file.data.state !== 'ready') {
|
|
17
|
-
for (let offset = 0; offset < bytes.length; offset += file.data.part_bytes) {
|
|
18
|
-
await api.uploadSubagentPart(file.data.file_id, offset / file.data.part_bytes,
|
|
19
|
-
bytes.subarray(offset, offset + file.data.part_bytes).toString('base64'));
|
|
20
|
-
}
|
|
21
|
-
await api.completeSubagentFile(file.data.file_id);
|
|
22
|
-
}
|
|
23
|
-
const job = await api.dispatchSubagent({kind:'summarize', goal:'핵심 내용과 근거를 정리하세요',
|
|
24
|
-
file_ids:[file.data.file_id]}, {idempotencyKey:'sample-summary-20261004-001'});
|
|
25
|
-
const result = await api.collectSubagent(job.data.job_id);
|
|
26
|
-
// 완료 후 evidence_ids를 조회하여 인용·해시를 직접 검토하세요.
|
|
27
|
-
// Once complete, fetch evidence_ids and independently review quotes and hashes.
|
|
28
|
-
console.log(result.data.state);
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
접수 응답은 작업 ID를 신속히 반환합니다. 같은 멱등 키와 같은 내용은 기존 작업을 반환하며 내용이 다르면 409입니다. 연결 종료는 취소가 아닙니다. `cancelSubagent`로 명시적으로 취소하세요. `retention:'none'`은 검수 완료 후 삭제하고 임시 보관은 최대 1시간입니다.
|
|
32
|
-
|
|
33
|
-
Submission returns a job ID promptly. Replay the same idempotency key and payload after response loss; a changed payload conflicts with HTTP 409. Disconnecting does not cancel work; use `cancelSubagent`. `retention:'none'` deletes after review, with temporary retention capped at one hour.
|
|
34
|
-
|
|
35
|
-
`subagentUsage`는 호출·토큰·예약·청구·해제·캐시·전송 지표를 반환합니다. 캐시 예상 회피 비용은 동일 작업의 최초 계산 비용 기준이며 주 모델의 실제 청구 절감액이 아닙니다.
|
|
36
|
-
|
|
37
|
-
`subagentUsage` returns calls, tokens, reservations, charges, releases, cache and transfer metrics. Estimated avoided cache cost uses the original job's calculated cost and is not a measured reduction in the main model's bill.
|
|
38
|
-
|
|
39
|
-
[전체 REST·MCP 가이드 / Full guide](https://apick.app/dev_guide/subagent)
|
|
1
|
+
# 서브에이전트 / Subagent
|
|
2
|
+
|
|
3
|
+
`apick-agent`는 설치형 상품이며 `quoteSkill`·`runSkill` 대신 전용 API를 사용합니다. 성공 작업의 확인된 원가에 40%를 가산하며 승인된 동일 결과 캐시는 무료입니다. 충전 잔액 외 상품 사용량 한도는 없습니다.
|
|
4
|
+
|
|
5
|
+
`apick-agent` is an installed-agent product. Use the dedicated API rather than `quoteSkill` or `runSkill`. Successful work is billed at confirmed model cost plus 40%; approved identical-result cache reuse is free. There are no product usage quotas beyond prepaid balance.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { ApickClient } from 'apick-api';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
const api = new ApickClient({ apiKey: process.env.APICK_API_KEY });
|
|
11
|
+
const bytes = Buffer.from('검토할 공개 문서입니다.');
|
|
12
|
+
const file = await api.createSubagentFile({
|
|
13
|
+
path: 'docs/sample.txt', bytes: bytes.length,
|
|
14
|
+
sha256: createHash('sha256').update(bytes).digest('hex'), retention: 'seven_days'
|
|
15
|
+
});
|
|
16
|
+
if (file.data.state !== 'ready') {
|
|
17
|
+
for (let offset = 0; offset < bytes.length; offset += file.data.part_bytes) {
|
|
18
|
+
await api.uploadSubagentPart(file.data.file_id, offset / file.data.part_bytes,
|
|
19
|
+
bytes.subarray(offset, offset + file.data.part_bytes).toString('base64'));
|
|
20
|
+
}
|
|
21
|
+
await api.completeSubagentFile(file.data.file_id);
|
|
22
|
+
}
|
|
23
|
+
const job = await api.dispatchSubagent({kind:'summarize', goal:'핵심 내용과 근거를 정리하세요',
|
|
24
|
+
file_ids:[file.data.file_id]}, {idempotencyKey:'sample-summary-20261004-001'});
|
|
25
|
+
const result = await api.collectSubagent(job.data.job_id);
|
|
26
|
+
// 완료 후 evidence_ids를 조회하여 인용·해시를 직접 검토하세요.
|
|
27
|
+
// Once complete, fetch evidence_ids and independently review quotes and hashes.
|
|
28
|
+
console.log(result.data.state);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
접수 응답은 작업 ID를 신속히 반환합니다. 같은 멱등 키와 같은 내용은 기존 작업을 반환하며 내용이 다르면 409입니다. 연결 종료는 취소가 아닙니다. `cancelSubagent`로 명시적으로 취소하세요. `retention:'none'`은 검수 완료 후 삭제하고 임시 보관은 최대 1시간입니다.
|
|
32
|
+
|
|
33
|
+
Submission returns a job ID promptly. Replay the same idempotency key and payload after response loss; a changed payload conflicts with HTTP 409. Disconnecting does not cancel work; use `cancelSubagent`. `retention:'none'` deletes after review, with temporary retention capped at one hour.
|
|
34
|
+
|
|
35
|
+
`subagentUsage`는 호출·토큰·예약·청구·해제·캐시·전송 지표를 반환합니다. 캐시 예상 회피 비용은 동일 작업의 최초 계산 비용 기준이며 주 모델의 실제 청구 절감액이 아닙니다.
|
|
36
|
+
|
|
37
|
+
`subagentUsage` returns calls, tokens, reservations, charges, releases, cache and transfer metrics. Estimated avoided cache cost uses the original job's calculated cost and is not a measured reduction in the main model's bill.
|
|
38
|
+
|
|
39
|
+
[전체 REST·MCP 가이드 / Full guide](https://apick.app/dev_guide/subagent)
|
package/docs/tts.md
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
# Gemini·ChatGPT TTS
|
|
2
|
-
|
|
3
|
-
기존 APICK 목소리와 자동 전환은 종료되었습니다. `createTtsJob`은 Gemini 기본 목소리 `Kore`를 사용합니다. `getTtsQuality`, `retryTtsJob`, `downloadTtsCandidate`의 기존 경로는 HTTP 409를 반환합니다.
|
|
4
|
-
|
|
5
|
-
Legacy APICK voices and automatic fallback have retired. `createTtsJob` now defaults to Gemini voice `Kore`. The legacy quality, retry and candidate endpoints return HTTP 409.
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
const job = await client.createTtsJob('기온은 오 도입니다.', { voiceId: 'Kore', normalizeText: false, idempotencyKey: 'tts-001' });
|
|
9
|
-
const dialogue = await client.createOpenAiTtsJob({
|
|
10
|
-
utterances: [{ speaker: '진행', text: '반갑습니다.' }, { speaker: '손님', text: '안녕하세요.' }],
|
|
11
|
-
speakers: { 진행: { voice_id: 'alloy' }, 손님: { voice_id: 'nova', emotion: 'calm' } },
|
|
12
|
-
multi_speaker: true, normalize_text: false
|
|
13
|
-
}, { idempotencyKey: 'tts-002' });
|
|
14
|
-
const voices = await client.listGeminiTtsVoices();
|
|
15
|
-
const otherVoices = await client.listOpenAiTtsVoices();
|
|
16
|
-
const options = await client.getTtsOptions();
|
|
17
|
-
const quote = await client.quoteTts({ engine: 'gemini', text: '본문', voice_id: 'Kore' });
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
`style`, `emotion`, `tone`, `accent`, `pace`(0.5–2), `pitch`(-12–12), `volume_gain_db`(-12–12)는 최상위·화자·발화에 지정할 수 있습니다. `speakers`는 화자 이름별 설정이고 `multi_speaker: true`는 Gemini 최대 2명, ChatGPT 최대 8명입니다. 엔진에 따라 표현 효과가 달라지며 정확한 속도 배율·반음·dB 적용을 보장하지 않습니다. 특히 ChatGPT는 수치의 방향을 낭독 지시로 전달합니다.
|
|
21
|
-
|
|
22
|
-
Style, emotion, tone, accent, pace, pitch and volume options can be set per request, speaker or utterance. Gemini supports up to two native speakers; ChatGPT supports up to eight speakers across utterances. Numeric acoustic adjustments are not guaranteed: ChatGPT receives directional instructions rather than precise rate, semitone or decibel controls.
|
|
23
|
-
|
|
24
|
-
정규화는 기본 켜짐이며 추가 스킬 요금이 합산됩니다. `normalize_text: false`(SDK 문자열 호출은 `normalizeText: false`)로 끌 수 있습니다. 정규화 입력은 발화 사이 줄바꿈을 포함해 최대 2,000자, 정규화 없이 최대 8,000자입니다. 견적은 `quoteTts`, 현재 목소리는 엔진별 목록, 옵션은 `getTtsOptions`로 조회합니다.
|
|
25
|
-
|
|
26
|
-
Normalization is enabled by default and costs extra. Disable it with `normalize_text: false` (`normalizeText: false` for the string SDK helper). Input limits are 2,000 characters including utterance separators with normalization, or 8,000 without it. Query a quote, engine-specific voices and supported options before submitting.
|
|
27
|
-
|
|
28
|
-
실제 AI 원가 × 고정된 환율 × 1.4에 정규화 요금을 더해 정산합니다. Gemini 프로모션 입력/출력 단가는 2026-12-31까지 백만 토큰당 $0.50/$6.00이며 2027-01-01 00:00 UTC부터 $1.00/$12.00입니다. 약 $0.54/시간은 참고값이며 시간 단위 청구가 아닙니다. 엔진별 요금이 다릅니다.
|
|
29
|
-
|
|
30
|
-
AI usage is charged at verified cost × the pinned exchange rate × 1.4, plus normalization. Gemini promotional input/output rates are $0.50/$6.00 per million tokens through 2026-12-31, then $1.00/$12.00 from 2027-01-01 00:00 UTC. The approximate hourly figure is informational; billing uses actual usage and differs by engine.
|
|
31
|
-
|
|
32
|
-
두 엔진은 같은 작업 조회·취소·MP3·원문 ASS 계약을 사용합니다. `billing`의 예약·해제·확정·환불을 확인하세요. MP3는 24kHz 모노 48kbps, 결과는 완료 후 24시간 보관합니다. MP3와 ASS는 각각 완료된 다운로드 1회만 허용하며 전송 중단은 재시도할 수 있습니다. 접수 응답 유실 시 같은 입력과 멱등 키를 재사용하세요. 서버·공급자 최종 실패는 정규화까지 환불하며, 연결 종료는 작업 취소가 아닙니다. 취소 시 이미 수행된 작업분을 정산합니다.
|
|
33
|
-
|
|
34
|
-
Both engines share job status, cancellation, MP3 and original-text ASS downloads. Check reserved, released, captured and refunded amounts in `billing`. MP3 is 24 kHz mono at 48 kbps. Files are kept for 24 hours; each permits one completed download, and interrupted transfers can be retried. Reuse the same input and idempotency key after a lost response. Final server/provider failures refund synthesis and normalization; disconnecting does not cancel work. Explicit cancellation settles work already performed.
|
|
1
|
+
# Gemini·ChatGPT TTS
|
|
2
|
+
|
|
3
|
+
기존 APICK 목소리와 자동 전환은 종료되었습니다. `createTtsJob`은 Gemini 기본 목소리 `Kore`를 사용합니다. `getTtsQuality`, `retryTtsJob`, `downloadTtsCandidate`의 기존 경로는 HTTP 409를 반환합니다.
|
|
4
|
+
|
|
5
|
+
Legacy APICK voices and automatic fallback have retired. `createTtsJob` now defaults to Gemini voice `Kore`. The legacy quality, retry and candidate endpoints return HTTP 409.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const job = await client.createTtsJob('기온은 오 도입니다.', { voiceId: 'Kore', normalizeText: false, idempotencyKey: 'tts-001' });
|
|
9
|
+
const dialogue = await client.createOpenAiTtsJob({
|
|
10
|
+
utterances: [{ speaker: '진행', text: '반갑습니다.' }, { speaker: '손님', text: '안녕하세요.' }],
|
|
11
|
+
speakers: { 진행: { voice_id: 'alloy' }, 손님: { voice_id: 'nova', emotion: 'calm' } },
|
|
12
|
+
multi_speaker: true, normalize_text: false
|
|
13
|
+
}, { idempotencyKey: 'tts-002' });
|
|
14
|
+
const voices = await client.listGeminiTtsVoices();
|
|
15
|
+
const otherVoices = await client.listOpenAiTtsVoices();
|
|
16
|
+
const options = await client.getTtsOptions();
|
|
17
|
+
const quote = await client.quoteTts({ engine: 'gemini', text: '본문', voice_id: 'Kore' });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`style`, `emotion`, `tone`, `accent`, `pace`(0.5–2), `pitch`(-12–12), `volume_gain_db`(-12–12)는 최상위·화자·발화에 지정할 수 있습니다. `speakers`는 화자 이름별 설정이고 `multi_speaker: true`는 Gemini 최대 2명, ChatGPT 최대 8명입니다. 엔진에 따라 표현 효과가 달라지며 정확한 속도 배율·반음·dB 적용을 보장하지 않습니다. 특히 ChatGPT는 수치의 방향을 낭독 지시로 전달합니다.
|
|
21
|
+
|
|
22
|
+
Style, emotion, tone, accent, pace, pitch and volume options can be set per request, speaker or utterance. Gemini supports up to two native speakers; ChatGPT supports up to eight speakers across utterances. Numeric acoustic adjustments are not guaranteed: ChatGPT receives directional instructions rather than precise rate, semitone or decibel controls.
|
|
23
|
+
|
|
24
|
+
정규화는 기본 켜짐이며 추가 스킬 요금이 합산됩니다. `normalize_text: false`(SDK 문자열 호출은 `normalizeText: false`)로 끌 수 있습니다. 정규화 입력은 발화 사이 줄바꿈을 포함해 최대 2,000자, 정규화 없이 최대 8,000자입니다. 견적은 `quoteTts`, 현재 목소리는 엔진별 목록, 옵션은 `getTtsOptions`로 조회합니다.
|
|
25
|
+
|
|
26
|
+
Normalization is enabled by default and costs extra. Disable it with `normalize_text: false` (`normalizeText: false` for the string SDK helper). Input limits are 2,000 characters including utterance separators with normalization, or 8,000 without it. Query a quote, engine-specific voices and supported options before submitting.
|
|
27
|
+
|
|
28
|
+
실제 AI 원가 × 고정된 환율 × 1.4에 정규화 요금을 더해 정산합니다. Gemini 프로모션 입력/출력 단가는 2026-12-31까지 백만 토큰당 $0.50/$6.00이며 2027-01-01 00:00 UTC부터 $1.00/$12.00입니다. 약 $0.54/시간은 참고값이며 시간 단위 청구가 아닙니다. 엔진별 요금이 다릅니다.
|
|
29
|
+
|
|
30
|
+
AI usage is charged at verified cost × the pinned exchange rate × 1.4, plus normalization. Gemini promotional input/output rates are $0.50/$6.00 per million tokens through 2026-12-31, then $1.00/$12.00 from 2027-01-01 00:00 UTC. The approximate hourly figure is informational; billing uses actual usage and differs by engine.
|
|
31
|
+
|
|
32
|
+
두 엔진은 같은 작업 조회·취소·MP3·원문 ASS 계약을 사용합니다. `billing`의 예약·해제·확정·환불을 확인하세요. MP3는 24kHz 모노 48kbps, 결과는 완료 후 24시간 보관합니다. MP3와 ASS는 각각 완료된 다운로드 1회만 허용하며 전송 중단은 재시도할 수 있습니다. 접수 응답 유실 시 같은 입력과 멱등 키를 재사용하세요. 서버·공급자 최종 실패는 정규화까지 환불하며, 연결 종료는 작업 취소가 아닙니다. 취소 시 이미 수행된 작업분을 정산합니다.
|
|
33
|
+
|
|
34
|
+
Both engines share job status, cancellation, MP3 and original-text ASS downloads. Check reserved, released, captured and refunded amounts in `billing`. MP3 is 24 kHz mono at 48 kbps. Files are kept for 24 hours; each permits one completed download, and interrupted transfers can be retried. Reuse the same input and idempotency key after a lost response. Final server/provider failures refund synthesis and normalization; disconnecting does not cancel work. Explicit cancellation settles work already performed.
|
package/examples/basic.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ApickClient } from 'apick-api';
|
|
2
|
-
|
|
3
|
-
const client = new ApickClient(process.env.APICK_API_KEY);
|
|
4
|
-
|
|
5
|
-
const business = await client.businessDetails('439-87-00761');
|
|
6
|
-
console.log(business.data);
|
|
7
|
-
console.log(business.meta);
|
|
8
|
-
|
|
9
|
-
const parcel = await client.trackParcelAuto('123456789012');
|
|
10
|
-
console.log(parcel.data);
|
|
1
|
+
import { ApickClient } from 'apick-api';
|
|
2
|
+
|
|
3
|
+
const client = new ApickClient(process.env.APICK_API_KEY);
|
|
4
|
+
|
|
5
|
+
const business = await client.businessDetails('439-87-00761');
|
|
6
|
+
console.log(business.data);
|
|
7
|
+
console.log(business.meta);
|
|
8
|
+
|
|
9
|
+
const parcel = await client.trackParcelAuto('123456789012');
|
|
10
|
+
console.log(parcel.data);
|
package/examples/commonjs.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
const { ApickClient } = require('apick-api');
|
|
2
|
-
|
|
3
|
-
async function main() {
|
|
4
|
-
const client = new ApickClient(process.env.APICK_API_KEY);
|
|
5
|
-
const result = await client.whois('apick.app');
|
|
6
|
-
console.log(result.data);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
main().catch((error) => {
|
|
10
|
-
console.error(error.message);
|
|
11
|
-
process.exitCode = 1;
|
|
12
|
-
});
|
|
1
|
+
const { ApickClient } = require('apick-api');
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const client = new ApickClient(process.env.APICK_API_KEY);
|
|
5
|
+
const result = await client.whois('apick.app');
|
|
6
|
+
console.log(result.data);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
main().catch((error) => {
|
|
10
|
+
console.error(error.message);
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
});
|
package/examples/files.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ApickClient } from 'apick-api';
|
|
2
|
-
|
|
3
|
-
const client = new ApickClient(process.env.APICK_API_KEY);
|
|
4
|
-
|
|
5
|
-
const ocr = await client.ocr('./receipt.jpg');
|
|
6
|
-
console.log(ocr.data.result.full_text);
|
|
7
|
-
|
|
8
|
-
const pdf = await client.htmlToPdf('<h1>APICK report</h1>', { pagination: true });
|
|
9
|
-
await pdf.save('./report.pdf');
|
|
1
|
+
import { ApickClient } from 'apick-api';
|
|
2
|
+
|
|
3
|
+
const client = new ApickClient(process.env.APICK_API_KEY);
|
|
4
|
+
|
|
5
|
+
const ocr = await client.ocr('./receipt.jpg');
|
|
6
|
+
console.log(ocr.data.result.full_text);
|
|
7
|
+
|
|
8
|
+
const pdf = await client.htmlToPdf('<h1>APICK report</h1>', { pagination: true });
|
|
9
|
+
await pdf.save('./report.pdf');
|
package/package.json
CHANGED
package/src/form.cjs
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Client-only multipart serialization. Each value occupies its own form part.
|
|
4
|
-
function createForm(payload, endpoint) {
|
|
5
|
-
const form = new FormData();
|
|
6
|
-
const typed = endpoint === '/rest/json_to_excel';
|
|
7
|
-
const forbidden = new Set(['__proto__', 'prototype', 'constructor']);
|
|
8
|
-
function visit(value, parts) {
|
|
9
|
-
if (parts.some(key => forbidden.has(key) || /[\[\]]/.test(key)) || parts.length > 12) throw new TypeError('Invalid form field path.');
|
|
10
|
-
const name = parts[0] + parts.slice(1).map(key => `[${key}]`).join('');
|
|
11
|
-
const cell = typed && parts[0] === 'data_list';
|
|
12
|
-
if (value === undefined || (value === null && !cell)) return;
|
|
13
|
-
if (value instanceof Blob) { form.append(name, value); return; }
|
|
14
|
-
if (value && typeof value === 'object' && Object.keys(value).length) {
|
|
15
|
-
if (cell) form.append('__apick_type' + parts.map(key => `[${key}]`).join(''), Array.isArray(value) ? 'array' : 'object');
|
|
16
|
-
for (const key of Object.keys(value)) visit(value[key], parts.concat(key));
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (cell && typeof value !== 'string') {
|
|
20
|
-
form.append('__apick_type' + parts.map(key => `[${key}]`).join(''), value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value);
|
|
21
|
-
}
|
|
22
|
-
if (!cell && value && typeof value === 'object') return;
|
|
23
|
-
form.append(name, value === null || typeof value === 'object' ? '' : String(value));
|
|
24
|
-
}
|
|
25
|
-
for (const key of Object.keys(payload || {})) visit(payload[key], [key]);
|
|
26
|
-
return form;
|
|
27
|
-
}
|
|
28
|
-
module.exports = { createForm };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Client-only multipart serialization. Each value occupies its own form part.
|
|
4
|
+
function createForm(payload, endpoint) {
|
|
5
|
+
const form = new FormData();
|
|
6
|
+
const typed = endpoint === '/rest/json_to_excel';
|
|
7
|
+
const forbidden = new Set(['__proto__', 'prototype', 'constructor']);
|
|
8
|
+
function visit(value, parts) {
|
|
9
|
+
if (parts.some(key => forbidden.has(key) || /[\[\]]/.test(key)) || parts.length > 12) throw new TypeError('Invalid form field path.');
|
|
10
|
+
const name = parts[0] + parts.slice(1).map(key => `[${key}]`).join('');
|
|
11
|
+
const cell = typed && parts[0] === 'data_list';
|
|
12
|
+
if (value === undefined || (value === null && !cell)) return;
|
|
13
|
+
if (value instanceof Blob) { form.append(name, value); return; }
|
|
14
|
+
if (value && typeof value === 'object' && Object.keys(value).length) {
|
|
15
|
+
if (cell) form.append('__apick_type' + parts.map(key => `[${key}]`).join(''), Array.isArray(value) ? 'array' : 'object');
|
|
16
|
+
for (const key of Object.keys(value)) visit(value[key], parts.concat(key));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (cell && typeof value !== 'string') {
|
|
20
|
+
form.append('__apick_type' + parts.map(key => `[${key}]`).join(''), value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value);
|
|
21
|
+
}
|
|
22
|
+
if (!cell && value && typeof value === 'object') return;
|
|
23
|
+
form.append(name, value === null || typeof value === 'object' ? '' : String(value));
|
|
24
|
+
}
|
|
25
|
+
for (const key of Object.keys(payload || {})) visit(payload[key], [key]);
|
|
26
|
+
return form;
|
|
27
|
+
}
|
|
28
|
+
module.exports = { createForm };
|