spotwork-api
v0.3.0
Published
SpotWORK (https://spotwork.biz) の案件・スポット情報を取得するための非公式クライアント
Readme
spotwork-api
SpotWORK の案件・スポット情報を取得するための非公式 npm モジュールです。 研究用途・読み取り専用のクライアントとして設計されています。
- TypeScript 製。型定義 (
dist/index.d.ts) 同梱。 - ランタイム依存ゼロ。Node.js 18 以降の標準
fetchのみを使用。 - トークン自動更新に対応。
refreshTokenがあれば期限切れを検知して自動で更新します。
インストール
npm install spotwork-apiクイックスタート
import { SpotworkClient } from 'spotwork-api';
// testacc.txt (Firebase の VerifyAssertionResponse) からアカウントを読み込む
const client = await SpotworkClient.from({ accountFile: './testacc.txt' });
// 近隣の案件
const orders = await client.getOrdersNear({ lat: 34.6937, lng: 135.5023, radiusInM: 3000 });
// 案件が出ている充電スポット
const works = await client.getBatteryWorkList({ lat: 34.6937, lng: 135.5023, limit: 100 });
// 範囲内の全スポット(案件の有無を問わない)
const shops = await client.getShopsNear({ lat: 34.6937, lng: 135.5023 }, { radiusMeters: 3000 });
// 全スポット(約 5.6 万件。limit 推奨)
const all = await client.getAllShops({ limit: 1000 });できること
| 機能 | メソッド | データ源 |
| --- | --- | --- |
| 近隣の案件(オーダー)を取得 | getOrdersNear({ lat, lng, radiusInM, limit }) | worker/orders API |
| 案件をもれなく取得(limit 切り詰め対策) | getOrdersNearExhaustive({ lat, lng, radiusInM, ... }) | スポット単位の分割クエリ |
| 案件の詳細 / スポット詳細 | getOrderDetail(id) / getSpotOrderDetail(id) | worker/orders API |
| 探す案件(looking-for-order) | getLookingForOrders({ lat?, lng? }) | worker/orders API |
| 案件が出ている充電スポット作業 | getBatteryWorkList({ lat, lng, limit }) | charge-spot API |
| 充電スポット案件をもれなく取得 | getBatteryWorkListExhaustive({ lat, lng, radiusInM, ... }) | スポット単位の分割クエリ |
| 充電スポット作業の詳細 | getBatteryWorkDetail(workId) | charge-spot API |
| 予約可能な充電スポット作業 | getReservableWorkList({ limit }) | charge-spot API |
| ブラックリスト PB スポット | getBlackListSpots({ lat, lng, limit }) | charge-spot API |
| 範囲内の全スポット(案件の有無を問わない) | getShopsNear(center, { radiusMeters, limit }) | Firestore SHOP コレクション |
| 全スポット(案件の有無を問わない。約 5.6 万件) | getAllShops({ limit, onlyActive, minLat, maxLat }) | Firestore SHOP コレクション |
| スポット 1 件を ID 指定で取得 | getShop(shopId) | Firestore SHOP コレクション |
もれなく取得(exhaustive)
getOrdersNear / getBatteryWorkList はサーバー側の件数上限(limit)で切り詰められ、ページングがありません。範囲内に案件が多いと取得漏れが発生します。
getOrdersNearExhaustive / getBatteryWorkListExhaustive は、切り詰め検知による適応アルゴリズムで「もれなく・最小リクエスト」を実現します。中心から 1 回クエリし、limit(200) 未満なら 1 クエリで完了、ちょうど 200 件のときのみクエリ計画で分割します。
// 全国のスポット一覧を 1 回だけ取得してキャッシュ(約 5.6 万件・Firestore 1 回)
await client.refreshSpotCache();
const result = await client.getOrdersNearExhaustive({
lat: 34.6937,
lng: 135.5023,
radiusInM: 3000, // 検索半径(m)
spotRadiusM: 500, // 計画モード時のクエリ半径(m)。既定 500
coverMarginM: 150, // 案件座標のズレの安全マージン(m)。既定 150
concurrency: 3, // 並列実行数。既定 3、上限 10
requestDelayMs: 0, // リクエスト間の待ち時間(ms)
onProgress: ({ spotIndex, spotCount }) => {
console.log(`${spotIndex + 1}/${spotCount}`);
},
});
console.log(result.orders, result.spotCount, result.queryCount, result.orderCount, result.truncatedSpots);- 切り詰め検知で最小リクエスト: 中心から 1 回クエリし、limit(200) 未満なら完了。ちょうど 200 件のときのみクエリ計画で分割(実測: 大阪 3km 圏・1,190 スポット・案件 34 件 → 1 クエリ)。
- 全国キャッシュ(
refreshSpotCache)を使うと、exhaustive /getShopsNearは Firestore を呼ばずにクライアント側で範囲フィルタします。 truncatedSpotsに載ったスポットは、1 回のクエリが limit(200) に達した(さらに範囲を狭める必要がある)スポットです。failedSpotsに載ったスポットは、クエリが失敗(429 など)したスポットです。全体は中断されず、制限回復後に再試行できます。- 対象は「radiusInM 内のスポットに紐づく案件」です。従来の
getOrdersNearはサーバー側のルーズな検索により半径外の案件も含むことがあります(意味論の違いに注意)。 - SHOP マスターは更新頻度が低いため、全国キャッシュを 1 日 1 回程度更新するのがおすすめです。キャッシュの鮮度(新しいスポットの反映タイミング) は docs/usage.md を参照。
「案件が出ているスポット」と「存在するスポット」の違い
getBatteryWorkListは「現在作業(案件)が有効なスポット」だけを返します。getShopsNear/getAllShopsは Firestore のSHOPコレクション(全スポットのマスターデータ)を返すため、案件の有無に関係なくスポットを取得できます。
アカウント情報
testacc.txt は Firebase Auth の VerifyAssertionResponse(Google ログイン成功時のレスポンス)そのものです。資格情報を含むため コミットしない でください(.gitignore に登録済み)。
アカウント情報の指定方法(優先順位):
accountFileオプション:testacc.txtのパスaccountJsonオプション:VerifyAssertionResponseのオブジェクトSPOTWORK_ACCOUNT_FILE環境変数:testacc.txtのパスuid/idToken/refreshTokenオプション- 環境変数:
SPOTWORK_UID/SPOTWORK_ID_TOKEN/SPOTWORK_REFRESH_TOKEN
// オプション指定
const client = await SpotworkClient.from({ accountFile: './testacc.txt' });
// 環境変数で指定(引数なし)
const client2 = await SpotworkClient.from();ID トークンの有効期限は 1 時間です。refreshToken があれば、期限間近や 401 のときに自動で更新されます。
エラーハンドリング
ApiError: API がエラーステータスを返したとき。httpStatus/code/dataを持ちます。AuthError: 認証情報が無効・不足のとき(トークン更新失敗など)。
import { ApiError, AuthError } from 'spotwork-api';
try {
await client.getOrdersNear({ lat, lng });
} catch (err) {
if (err instanceof ApiError) console.log(`HTTP ${err.httpStatus}: ${err.message}`);
else if (err instanceof AuthError) console.log(`認証エラー: ${err.message}`);
}CLI デモ(実 API での動作確認)
# デフォルト(大阪)で実行
npx tsx examples/demo.ts
# 座標と半径を指定(東京駅 3km 圏)
npx tsx examples/demo.ts 35.6812 139.7671 3000
# 結果を JSON で保存
set SPOTWORK_OUT=out.json && npx tsx examples/demo.ts 35.6812 139.7671 3000
# アカウントファイルを環境変数で指定
set SPOTWORK_ACCOUNT_FILE=testacc.txt && npx tsx examples/demo.ts開発
npm install # 依存のインストール
npm test # モック fetch によるユニットテスト
npm run build # TypeScript のビルド(dist/)
npm run demo # 実 API でのデモ実行ドキュメント
- docs/usage.md — 詳細な使い方(アカウント・各 API・型)
- docs/api.md — 全公開 API のリファレンス
- docs/best-practices.md — 実用上の推奨・絶対に守るべきこと(レート制限・利用規約・資格情報の扱い)
- docs/publishing.md — npm への公開方法(ローカル / GitHub Actions OIDC)
- CHANGELOG.md — 変更履歴
公開(リリース)方法
タグ v* をプッシュすると、GitHub Actions が npm へ自動公開します(OIDC 認証・トークン不要)。詳細は docs/publishing.md を参照してください。
npm version patch
git push --tags実装メモ(API の裏側)
- 案件 API / 充電スポット API は
Authorization: Bearer <idToken>に加えてOrigin: https://spotwork.bizが必要です(無いと CORS で弾かれます)。 - Firebase(トークン更新・Firestore)には
Referer: https://spotwork.biz/が必要です(無いと 403)。 - HTTP 429(レート制限)は自動リトライされます(Retry-After ヘッダを優先し、無ければ指数バックオフ。既定 3 回)。充電スポット API は制限が厳しいため、exhaustive 系は
concurrencyとrequestDelayMsで負荷を抑えてください。 - Firestore は
SHOP/RECENTWORK/RESERVED_SPOT/BONUSSPOT/TICKERなどの限られたコレクションのみ読み取り可能です(ORDERなどは権限なし)。 getShopsNearは複合インデックス不要にするため、緯度のみの範囲クエリ(単一フィールド)で候補を取得し、経度・距離はクライアント側で判定しています。Firestore REST のrunQueryは全結果を単一レスポンスで返します(ページングは不要。もしnextPageTokenが返された場合は黙って切り詰めずエラーにします)。
免責事項
非公式クライアントです。研究目的での利用を想定しています。利用前に docs/best-practices.md(レート制限・利用規約・資格情報の扱い)を必ずお読みください。 過剰なリクエストは避け、利用にあたっては SpotWORK の利用規約をご確認ください。
