スケジュールを取得する
const url = 'https://api.example.com/site-control/v1/sites/st_7k2m9q/desired?horizon_hours=3';const options = { method: 'GET', headers: {'If-None-Match': '"9f2c1a7e"', Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.example.com/site-control/v1/sites/st_7k2m9q/desired?horizon_hours=3' \ --header 'Authorization: Bearer <token>' \ --header 'If-None-Match: "9f2c1a7e"'EMS が定期的に呼び出して、サイトで実行すべき最新のスケジュールを、指定した期間の分だけ取得する。スケジュールはサイトに1つで、同じサイトの EMS には、同じ期間を指定すれば同じものが返る。
期間は horizon_hours で指定する。返るのは、現在時刻を含む区間の開始から、現在時刻の horizon_hours 時間後を含む区間の終わりまでである。終わりを区間の境界にそろえるのは、呼ぶたびに期間の終わりが秒単位でずれて中身が変わり、304 が返らなくなるのを防ぐためである。J-EMS の計画がまだ決まっていない時間帯(翌日の市場が約定する前など)は含まれず、その場合 horizon_end は指定より手前になる。
毎回スケジュール全体を受け取る必要はない。前回受け取った ETag を If-None-Match に入れて呼び出せば、指定した期間の中身が変わっていないときは本文のない 304 が返る。EMS はそのまま今のスケジュールの実行を続ければよい。ETag は返した期間の中身ごとに付くので、期間の中の計画が変わったときのほか、時間が進んで期間が区間1つ分ずれたときにも変わる。
1回の応答に含まれる区間の数は、期間の中で指示が変わる回数で決まる。通常は1日あたり数十件で、最も多い場合でも期間の分数(24 時間で 1,440 件)を超えない。Accept-Encoding: gzip を付けて呼び出せば、J-EMS は応答を圧縮して返す。
呼び出す間隔は、直近に受け取ったスケジュールの poll_interval_seconds を上限とする。J-EMS は、指令を早く届けたい時間帯の前にこの値を小さくすることがある。EMS は自分の判断でこれより短い間隔で呼び出してもよいが、同じ EMS からの呼び出しは 1 秒に1回までとする。それより頻繁な呼び出しには 429 が返る。
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Example
st_7k2m9qサイト ID。サイトの登録時に J-EMS が発行する。
Header Parameters
Section titled “Header Parameters”Example
"9f2c1a7e"EMS が保持しているスケジュールの ETag。前回の応答の ETag をそのまま送る。
Query Parameters
Section titled “Query Parameters”取得する期間。現在時刻から何時間先までを含めるか。通信が途絶えたときに運転を続けられる時間もこの長さで決まる。
Responses
Section titled “Responses”指定した期間のスケジュール。If-None-Match と中身が異なるとき、またはヘッダーがないときに返る。
指定した期間のスケジュール。EMS は受け取ったら、手元のスケジュールを丸ごと置き換える。
object
最後の区間の終了時刻(この時刻は含まない)。EMS はこの時刻を過ぎたら、新しいスケジュールを受け取るまで IDLE を保つ。
最初の区間の開始時刻。現在時刻を含む区間の開始時刻になる。
J-EMS がこのスケジュールを返した時刻。参考情報。
RUNNING:区間に従って運転する。
STOPPED:PCS を停止状態にし、RUNNING のスケジュールが届くまで区間を無視する。
以後 desired を呼び出す間隔の上限(秒)。EMS はこれより長い間隔を空けない。短くするのは自由だが、1 秒に1回までとする。
start の昇順に並ぶ。最初の区間の start は horizon_start と一致し、どの区間の start も horizon_end より前にある。
start から次の区間の start まで(最後の区間は horizon_end まで)の間にすること。mode によって、ほかのどのフィールドが入るかが決まる。
| mode | 必須フィールド | 意味 |
|---|---|---|
IDLE |
なし | 蓄電池を 0 kW に保つ。 |
POWER |
power_kw |
電力の設定値を保つ。reference_point を省略したときは蓄電池での値。 |
FREQUENCY_CONTROL |
fcr_capacity_kw、power_kw |
蓄電池での基準出力 power_kw を中心に、最大 fcr_capacity_kw の一次調整力を提供する。 |
そのモードに挙げていないフィールドは入らない。
object
その区間に約定した一次調整力の容量。
POWER では設定値、FREQUENCY_CONTROL では蓄電池での基準出力。正は放電・逆潮流。
POWER モードで power_kw をどこで満たすか。省略したときは BATTERY。
BATTERY:蓄電池そのもの。併設の発電はそのまま通す。
METER:系統の計量点での正味の潮流。差は蓄電池が吸収または供給する。例:出力制御中に 0 で逆潮流ゼロ。
区間の開始時刻。分の境界(秒が 0)にそろう。
このスケジュールの中身を表す識別子で、ETag と同じ値。中身が同じなら同じ値になり、違えば違う値になる。大小の比較はできない。
Example
{ "horizon_end": "2026-10-01T13:00:00+09:00", "horizon_start": "2026-10-01T10:00:00+09:00", "issued_at": "2026-10-01T10:00:00+09:00", "operating_state": "RUNNING", "poll_interval_seconds": 10, "slots": [ { "fcr_capacity_kw": 1500, "mode": "IDLE", "power_kw": -500, "reference_point": "BATTERY", "start": "2026-10-01T10:00:00+09:00" } ], "version": "b41e08d3"}Headers
Section titled “Headers”Example
"b41e08d3"このスケジュールの中身を表す識別子(引用符付き)。本文の version と同じ値。次回の If-None-Match にそのまま送る。
指定した期間の中身は If-None-Match と同じ。今のスケジュールの実行を続ける。
horizon_hours が 1〜24 の範囲にない。
object
400 のときの、フィールド単位の問題。
object
TIME_WINDOW_INVALID:区間の start が昇順でない、分の境界にない、最初の区間の start が horizon_start と一致しない、horizon_end 以降にある。出力制御スケジュールでは start >= end。
EXPIRED:horizon_end がすでに過ぎている。
DUPLICATE:同じ sample_id が別の内容に使われた。
INTERNAL_ERROR:受信側の障害。そのまま再試行する価値があるのはこのコードだけ。
問題のあるフィールドへのパス。
Example
{ "code": "UNAUTHORIZED", "errors": [ { "code": "MALFORMED", "field_path": "slots[2].power_kw" } ]}Bearer トークンがない、または無効。
object
400 のときの、フィールド単位の問題。
object
TIME_WINDOW_INVALID:区間の start が昇順でない、分の境界にない、最初の区間の start が horizon_start と一致しない、horizon_end 以降にある。出力制御スケジュールでは start >= end。
EXPIRED:horizon_end がすでに過ぎている。
DUPLICATE:同じ sample_id が別の内容に使われた。
INTERNAL_ERROR:受信側の障害。そのまま再試行する価値があるのはこのコードだけ。
問題のあるフィールドへのパス。
Example
{ "code": "UNAUTHORIZED", "errors": [ { "code": "MALFORMED", "field_path": "slots[2].power_kw" } ]}トークンは有効だが、このサイト向けに発行されたものではない。
object
400 のときの、フィールド単位の問題。
object
TIME_WINDOW_INVALID:区間の start が昇順でない、分の境界にない、最初の区間の start が horizon_start と一致しない、horizon_end 以降にある。出力制御スケジュールでは start >= end。
EXPIRED:horizon_end がすでに過ぎている。
DUPLICATE:同じ sample_id が別の内容に使われた。
INTERNAL_ERROR:受信側の障害。そのまま再試行する価値があるのはこのコードだけ。
問題のあるフィールドへのパス。
Example
{ "code": "UNAUTHORIZED", "errors": [ { "code": "MALFORMED", "field_path": "slots[2].power_kw" } ]}サイトが存在しない。
object
400 のときの、フィールド単位の問題。
object
TIME_WINDOW_INVALID:区間の start が昇順でない、分の境界にない、最初の区間の start が horizon_start と一致しない、horizon_end 以降にある。出力制御スケジュールでは start >= end。
EXPIRED:horizon_end がすでに過ぎている。
DUPLICATE:同じ sample_id が別の内容に使われた。
INTERNAL_ERROR:受信側の障害。そのまま再試行する価値があるのはこのコードだけ。
問題のあるフィールドへのパス。
Example
{ "code": "UNAUTHORIZED", "errors": [ { "code": "MALFORMED", "field_path": "slots[2].power_kw" } ]}リクエストが多すぎる。指数バックオフで再試行する。
object
400 のときの、フィールド単位の問題。
object
TIME_WINDOW_INVALID:区間の start が昇順でない、分の境界にない、最初の区間の start が horizon_start と一致しない、horizon_end 以降にある。出力制御スケジュールでは start >= end。
EXPIRED:horizon_end がすでに過ぎている。
DUPLICATE:同じ sample_id が別の内容に使われた。
INTERNAL_ERROR:受信側の障害。そのまま再試行する価値があるのはこのコードだけ。
問題のあるフィールドへのパス。
Example
{ "code": "UNAUTHORIZED", "errors": [ { "code": "MALFORMED", "field_path": "slots[2].power_kw" } ]}サーバーの一時的な障害。指数バックオフで再試行する。
object
400 のときの、フィールド単位の問題。
object
TIME_WINDOW_INVALID:区間の start が昇順でない、分の境界にない、最初の区間の start が horizon_start と一致しない、horizon_end 以降にある。出力制御スケジュールでは start >= end。
EXPIRED:horizon_end がすでに過ぎている。
DUPLICATE:同じ sample_id が別の内容に使われた。
INTERNAL_ERROR:受信側の障害。そのまま再試行する価値があるのはこのコードだけ。
問題のあるフィールドへのパス。
Example
{ "code": "UNAUTHORIZED", "errors": [ { "code": "MALFORMED", "field_path": "slots[2].power_kw" } ]}