Lovely Routerドキュメント
キーを取得
ブラウザエージェント

人による確認

エージェントからの質問、送信前の承認、CAPTCHA のためのブラウザの引き渡しに対応できます。いつでもあなたが操作を引き継ぐこともできます。

人の手が必要なステップもあります。スマートフォンに届くワンタイムコード、購入前の判断、CAPTCHA などです。エージェントは実行を一時停止して何が必要かを伝え、あなたが返信すると再開します。あなたのコード(またはそれを使う人)は、1 つのエンドポイント POST /v1/browser/runs/{id}/reply で返信します。

実行に必要なものneeds.kind返信の内容
エージェントが持っていない情報question{"answer": "…"} または {"secret": "…"}
送信前のあなたの了承approval{"approve": true} または {"approve": false, "reason": "…"}
ブラウザでの人の操作takeoverライブビューで操作を終えてから {"done": true} または {"failed": true}

一時停止の仕組み#

エージェントがあなたを必要とすると、実行の status は waiting_for_human になり、needs に必要なものが示されます(一部省略)。

JSON
{
  "id": "brun_5b7d9f1a3c5e7a9b1d3f5a7c",
  "object": "browser_run",
  "status": "waiting_for_human",
  "steps": 6,
  "current_url": "https://bank.example.com/verify",
  "needs": {
    "kind": "question",
    "prompt": "What is the 6-digit code sent to your phone ending 42?",
    "expects_secret": true,
    "screenshot": "/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/screenshot",
    "since": "2026-10-11T09:31:12.000Z",
    "reply": "/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/reply"
  }
}
フィールド意味
needs.kindquestion、approval、takeover のいずれか
needs.promptエージェントが必要としているもの。そのまま人に見せられる 1 文
needs.screenshot一時停止が始まった時点のページの JPEG のパス。API キーを付けて取得します。
needs.since一時停止が始まった日時
needs.reply返信先のパス

実行が待機している間:

  • ブラウザは同じページで開いたままなので、エージェントは停止した場所からそのまま再開します。
  • ?wait= 付きのステータス取得は、待機せずにすぐに返ります。返信するか、独自の間隔でポーリングしてください。
  • Webhook を設定している場合は、run.waiting イベントが届きます。
  • 一時停止ごとに 1 分が含まれます。それを超える待機には 1 分あたり $0.005 かかります(料金)。
  • human_timeout_minutes(デフォルト 30)以内に誰も返信しないと、実行は timed_out で終了し、エラーは no human response within 30 minutes になります。

コードで一時停止に対応する#

次のループは、承認やコードを求める可能性のあるタスクを実行し、ターミナルの前にいる人に尋ねます。

import os
import requests

API = "https://lovelyrouter.com/v1/browser"
H = {"Authorization": f"Bearer {os.environ['LOVELY_API_KEY']}"}

run = requests.post(f"{API}/runs", headers=H, json={
    "goal": "In the rewards shop, redeem 500 points for the Blue Mug and tell me the order number.",
    "start_url": "https://shop.example.com/rewards",
    "profile": "shop-login",
    "approve": "risky",
}).json()

while run["status"] in ("running", "waiting_for_human"):
    if run["status"] == "waiting_for_human":
        needs = run["needs"]
        if needs["kind"] == "approval":
            print(needs["prompt"], *needs["fields"], sep="\n  ")
            reply = {"approve": input("Approve? [y/N] ").strip().lower() == "y"}
        elif needs["kind"] == "question":
            value = input(needs["prompt"] + " ")
            reply = {"secret": value} if needs["expects_secret"] else {"answer": value}
        else:  # takeover
            print(needs["prompt"], "\nOpen:", needs["live_view_url"])
            input("Press Enter when you are done. ")
            reply = {"done": True}
        # A 409 here means the run already moved on; the next status request shows where it is.
        requests.post(f"{API}/runs/{run['id']}/reply", headers=H, json=reply)
    run = requests.get(f"{API}/runs/{run['id']}", headers=H, params={"wait": 30}).json()

print(run["status"], run["answer"])

ターミナルの前にいない人には、自社のアプリ、チャット、メールなどで needs.prompt(とスクリーンショットまたはライブビューのリンク)を送り、返事が届いたらそれを POST してください。いつ送ればよいかは Webhook が知らせます。

質問に答える#

ページがゴールに含まれていない情報を求めると、エージェントは質問します。スマートフォンやメールに届いた確認コード、足りない情報、あなたにしかできない選択などです。返信方法は 2 つあります。

  • {"answer": "…"}:エージェントが読んで使うテキスト。最大 2,000 文字。詳細情報や選択に使います:{"answer": "Use the billing address, not the shipping one."}
  • {"secret": "…"}:エージェントが一切見ることなくページに入力する値。最大 4,096 文字。ワンタイムコードに使います:{"secret": "481923"}

質問がコード、パスワード、PIN、ワンタイムコードを求めている場合、needs.expects_secret は true になります。シークレットでの返信は、質問した時点で実行が開いていたサイトでのみ入力でき、そのサイトのテキスト系の入力欄であれば、パスワード欄や PIN 欄も含めてどこにでも入力できます。他のシークレットと同様に、6 文字以上の値は実行の出力に現れる箇所で [secret:<name>] に置き換えられ、値は実行の終了時に削除されます。毎回の実行で使うパスワードは、エージェントが尋ねなくて済むよう、代わりにボールトに保存してください。

cURL
curl https://lovelyrouter.com/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/reply \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"secret": "481923"}'

認証アプリのコードには、代わりに TOTP シードをボールトに保存してください。エージェントが現在のコードを自分で入力するため、質問する必要がなくなります。二要素認証のコードを参照してください。

送信前に承認する#

approve は、何かを送信する操作の前に、エージェントがどの場合に止まってあなたの了承を求めるかを決めます。対象となる操作は、フォームを送信するボタン、購入・予約・支払い・メッセージ・削除を示すラベルのボタン(「Buy now」「Book」「Pay」「Send」「Delete」など)、そして検索ボックスではないフォームの入力欄で Enter キーを押してそのフォームを送信することです。リンクでは一時停止しません。リンクをたどってもページが開くだけだからです。

approveエージェントが一時停止するタイミング
risky(デフォルト)それらの操作のうち、取り消せない、または重大な結果を伴うと判断したものの前:支払いや購入、注文や予約、メッセージやメールの送信、データの削除、応募の提出。ログイン、検索、ページ移動、フォームの途中のステップはそのまま進めます。
submitそれらの操作すべての前。
none一時停止しません。エージェントが自分で送信します。

その他の値は 400 で拒否されます。承認のための一時停止は次のようになります。

JSON
{
  "status": "waiting_for_human",
  "needs": {
    "kind": "approval",
    "prompt": "Approve: click button \"Redeem 500 points\" on Blue Mug – Rewards?",
    "fields": ["Quantity: \"1\"", "Ship to: \"Ada Lovelace, 12 Main St\"", "Gift wrap: unchecked"],
    "action": "Redeem 500 points",
    "screenshot": "/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/screenshot",
    "since": "2026-10-11T09:33:40.000Z",
    "reply": "/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/reply"
  }
}

fields にはフォームの現在の値が並び、action はクリックしようとしているボタンのラベルです。次のように返信します。

  • {"approve": true}:エージェントは次のステップでそのボタンをクリックします。
  • {"approve": false, "reason": "Wrong address"}:エージェントは送信しません。理由(最大 500 文字)を読み、別の方法を試すか、作業を終えて理由を報告します。{"deny": true} も同じ動作です。
cURL
curl https://lovelyrouter.com/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/reply \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"approve": true}'

CAPTCHA のために引き渡す#

エージェントは CAPTCHA や「人間であることを確認してください」といったチェックを解けません。ページにそれらが表示されたとき、または人にしかできないステップに来たときは、needs.kind を takeover にしてブラウザをあなたに引き渡します。

JSON
{
  "status": "waiting_for_human",
  "needs": {
    "kind": "takeover",
    "prompt": "Please solve the CAPTCHA and press Continue.",
    "live_view_url": "https://…",
    "live_view_expires_at": "2026-10-11T10:35:02.000Z",
    "screenshot": "/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/screenshot",
    "since": "2026-10-11T09:35:02.000Z",
    "reply": "/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/reply"
  }
}
  1. ブラウザで live_view_url を開きます。エージェントのブラウザタブがリアルタイムで表示され、そこでクリックや入力ができます。リンクの有効期間は 1 時間です。
  2. prompt の指示どおりに操作します。
  3. {"done": true} を返信します。必要に応じて、エージェントが読む note(最大 500 文字)を付けられます。例:"Solved the CAPTCHA, now on the search results"。操作できなかった場合は {"failed": true, "note": "…"} を返信します。

引き渡しが終わったことを実行が自分で検知した場合は、返信を待たずに再開します。その後の返信には 409 が返りますが、無視して構いません。エージェントは行き詰まったときにも引き渡しを行います。3 回連続でステップに失敗したとき、または同じ操作を進展なく 3 回繰り返したときです。

CAPTCHA や「人間であることを確認してください」のページでの引き渡しは、1 回の実行につき最大 2 回です。その後もまだ表示される場合、または引き渡された人が {"failed": true} を返信した場合、実行はエラー blocked by the website で failed として終了します。ブラウザを完全にブロックするページ(アクセス拒否、403)も、人でも通過できないため同様に終了します。"human": false で開始した実行で人間による確認が表示された場合も同様です。

自分で操作を引き継ぐ#

エージェントに進め方を示したいときや、ステップを手作業で終わらせたいときなど、いつでもブラウザを引き継げます。

cURL
curl https://lovelyrouter.com/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/takeover \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"instructions": "I will pick the delivery slot myself."}'

リクエストはすぐに 202 を返します。実行は進行中のステップが終わった後に一時停止し、次のステータスでは needs.kind が takeover になり、live_view_url が含まれます。ライブビューで必要な操作を行い、{"done": true, "note": "Picked Saturday 10–12"} で操作を戻します。エージェントは、あなたが最後に開いていたページから再開します。

instructions(任意、最大 500 文字)は一時停止の prompt になります。実行がすでに質問や承認で待機中の場合、引き継ぎはそれに返信した後に始まります。終了した実行には 409 と This run has finished が返ります。

タイムアウトを設定する#

human_timeout_minutes は、一時停止ごとに返信を待つ時間を設定します。1〜1,440 分で、デフォルトは 30 分です(クラウドエージェントが開始した実行は、通常見ている人がいないため 5 分)。クラウドエージェントのセッションが終了すると、未完了のブラウザ実行は停止されます。タイマーは一時停止のたびにリセットされます。タイムアウトした実行は timed_out で終了し、ステップ分と課金対象の待機時間分が請求されます。

タイムアウトを長くすると返信までの猶予が増えますが、その分多くの残高が仮押さえされます。仮押さえ額には、タイムアウトまでの 1 回分の待機が 1 分あたり $0.005 で含まれるためです。仮押さえの仕組みを参照してください。

人を介さずに実行する#

無人で動かすジョブでは、一時停止をオフにします。

JSON
{
  "goal": "Get the current mortgage rates table.",
  "start_url": "https://bank.example.com/rates",
  "human": false,
  "approve": "none"
}
  • "human": false:エージェントは質問も引き渡しもしません。情報が足りない場合や行き詰まった場合も、できる限り作業を続けます。その結果、実行が failed で終了することがあります。
  • "approve": "none":承認を待たずにフォームを送信します。

両方を設定すると、実行は待機時間分を仮押さえしません。

返信のリファレンス#

POST /v1/browser/runs/{id}/reply は、次のボディのいずれか 1 つだけを受け付けます。

ボディ対象の needs.kind効果
{"answer": "…"}questionエージェントがテキストを読みます(最大 2,000 文字)。
{"secret": "…"}questionエージェントが値を見ずに、現在のサイトで入力します(最大 4,096 文字)。
{"approve": true}approvalエージェントが次のステップでボタンをクリックします。
{"approve": false, "reason": "…"} または {"deny": true, "reason": "…"}approvalエージェントは送信せず、理由を読みます。
{"done": true, "note": "…"}takeoverエージェントが現在のページから再開します。
{"failed": true, "note": "…"}takeover人が完了できなかったことを踏まえて、エージェントが再開します。

レスポンスでは、status が running に戻った実行が返ります。エラー:

ステータスメッセージ原因
400Reply with one of: …ボディが上記のどの形にも一致しません。
404No such runID が間違っているか、実行が別のアカウントのものです。
409This run is not waiting for a reply実行が進行中か、すでに終了しています。
409This run is waiting for a reply of kind "question"返信が needs.kind と一致しません(メッセージに期待される種類が示されます)。