Готовые сценарии
Три готовых сценария с личным ключом: задача с файлом, чат с уточнением, обработка ошибок — и чек-лист вызовов для ИИ-агента.
Страница продолжает «Личный ключ». Здесь три готовых
сценария, каждый с объяснением по шагам и двумя скриптами: на bash с curl и jq
и на Python 3 с библиотекой requests. Для ИИ-агента — чек-лист тех же вызовов.
Справочник запросов — в разделах «Задачи» и «Чаты».
Переменные скриптов
В скриптах две переменные окружения, общие для всех сценариев. Это переменные вашего скрипта, не настройки портала:
FORGE_URL— адрес портала, по которому вы открываете его в браузере. В примерах этоhttps://portal.example.com.FORGE_API_KEY— ваш личный ключ.
Скрипт сценария 2 дополнительно ждёт TASK_TYPE_ID — идентификатор типа задачи.
Ключ передаётся в заголовке Authorization: Bearer … каждого запроса.
Типичный сценарий
Порядок действий, который повторяют все сценарии:
-
Создайте ключ в разделе «Настройки» → «API-ключи» портала — как это сделать.

Кнопка «Создать ключ» в форме создания ключа -
POST /api/tasks— запустите задачу с файлами. Ответ201содержитidзадачи. -
Опрашивайте
GET /api/tasks/:idраз в 5 секунд, покаstatusне станетdone,partialилиfailed. -
GET /api/tasks/:id/files— список файлов,GET /api/tasks/:id/files/:fileId/download— скачивание каждого. -
Для чата:
POST /api/chat-sessionsсоздаёт сессию,POST /api/chat-sessions/:id/messagesотправляет вопрос,GET /api/chat-sessions/:id— опрос до готовности ответа.
Сценарий 1. Задача с файлом на входе
Скрипт запускает задачу типа «Сводка по продажам» с файлом на входе, дожидается результата и скачивает файлы.
- Запрос
POST /api/tasksотправляет slug типа, параметры в JSON-строкеparamsи файл в полеfiles; полный список полей — в разделе «Задачи». Портал отвечает201иidзадачи. - Затем скрипт опрашивает
GET /api/tasks/:idраз в 5 секунд. Ожидание останавливается на статусахdone(выполнена),partial(часть шагов не дала результата) иfailed(ошибка). GET /api/tasks/:id/filesвозвращает файлы задачи. Скрипт скачивает только файлы сdirection: "output"— это результат, а не вход.- Каждый файл скачивается запросом
GET /api/tasks/:id/files/:fileId/download. Имя файла приходит в заголовкеContent-Disposition, иcurl -OJсохраняет файл под этим именем.
Два ограничения на запуск. Повторный запуск того же типа раньше, чем через 60 секунд
после предыдущего, портал отклоняет с кодом 429 и сообщением «Подождите N сек.
перед повторным запуском этой задачи.». У учётной записи может быть лимит задач в
час — он тоже отвечает 429, но с числом минут в сообщении. Оба случая разбирает
сценарий 3.
bash
#!/usr/bin/env bash
set -euo pipefail
: "${FORGE_URL:?укажите адрес портала}"
: "${FORGE_API_KEY:?укажите личный ключ}"
AUTH="Authorization: Bearer ${FORGE_API_KEY}"
# 1. Запуск задачи с файлом на входе
TASK_ID=$(curl -sS -X POST "${FORGE_URL}/api/tasks" \
-H "$AUTH" \
-F "taskTypeSlug=sales-summary" \
-F "params={\"period\":\"2026-33\"}" \
-F "visibility=private" \
-F "files=@продажи-33-неделя.xlsx" \
| jq -r '.data.id')
echo "задача: ${TASK_ID}"
# 2. Ожидание завершения: опрос раз в 5 секунд
while :; do
STATUS=$(curl -sS "${FORGE_URL}/api/tasks/${TASK_ID}" -H "$AUTH" | jq -r '.data.status')
case "${STATUS}" in
done|partial|failed) break ;;
esac
sleep 5
done
echo "статус: ${STATUS}"
# 3. Список файлов задачи
curl -sS "${FORGE_URL}/api/tasks/${TASK_ID}/files" -H "$AUTH" \
| jq -r '.data[] | [.filename, .direction, (.isFinalResult|tostring)] | @tsv'
# 4. Скачивание выходных файлов
for FILE_ID in $(curl -sS "${FORGE_URL}/api/tasks/${TASK_ID}/files" -H "$AUTH" \
| jq -r '.data[] | select(.direction == "output") | .id'); do
curl -sS -OJ "${FORGE_URL}/api/tasks/${TASK_ID}/files/${FILE_ID}/download" -H "$AUTH"
donePython
import json
import os
import time
from pathlib import Path
import requests
FORGE_URL = os.environ["FORGE_URL"].rstrip("/")
HEADERS = {"Authorization": f"Bearer {os.environ['FORGE_API_KEY']}"}
POLL_SECONDS = 5
def main() -> None:
# 1. Запуск задачи с файлом на входе
with open("продажи-33-неделя.xlsx", "rb") as fh:
response = requests.post(
f"{FORGE_URL}/api/tasks",
headers=HEADERS,
data={
"taskTypeSlug": "sales-summary",
"params": json.dumps({"period": "2026-33"}),
"visibility": "private",
},
files={"files": ("продажи-33-неделя.xlsx", fh)},
timeout=120,
)
response.raise_for_status()
task_id = response.json()["data"]["id"]
print("задача:", task_id)
# 2. Ожидание завершения: опрос раз в 5 секунд
while True:
task = requests.get(
f"{FORGE_URL}/api/tasks/{task_id}", headers=HEADERS, timeout=30
).json()["data"]
if task["status"] in ("done", "partial", "failed"):
break
time.sleep(POLL_SECONDS)
print("статус:", task["status"])
# 3. Файлы задачи
files = requests.get(
f"{FORGE_URL}/api/tasks/{task_id}/files", headers=HEADERS, timeout=30
).json()["data"]
# 4. Скачивание выходных файлов
for item in files:
if item["direction"] != "output":
continue
stream = requests.get(
f"{FORGE_URL}/api/tasks/{task_id}/files/{item['id']}/download",
headers=HEADERS,
timeout=300,
stream=True,
)
stream.raise_for_status()
Path(item["filename"]).write_bytes(stream.content)
print("сохранено:", item["filename"])
if __name__ == "__main__":
main()Сценарий 2. Чат: вопрос, ответ, уточнение
Скрипт открывает чат по типу задачи, задаёт вопрос, дожидается ответа, печатает его и задаёт уточнение.
POST /api/chat-sessionsсоздаёт сессию; поля запроса — в разделе «Чаты». Обязательное поле —taskTypeId, идентификатор типа задачи. Возьмите его из ответаGET /api/tasks/:idлюбой вашей задачи этого типа — это полеtaskTypeId.POST /api/chat-sessions/:id/messagesотправляет вопрос. Ответ на этот запрос — поток событий; он закрывается, когда ход завершён (событиеmessage_complete), или когда чат был занят (событиеqueued). Ход выполняется, пока соединение открыто; если закрыть его раньше, ход остановится. Скрипт держит соединение, пока портал не завершит ход.- Затем скрипт опрашивает
GET /api/chat-sessions/:idраз в 3 секунды, пока последнее сообщение в списке не станет ответом ассистента с финальным статусомcompleted,stoppedилиfailed. Послеmessage_completeпервый же опрос увидит готовый ответ; опрос нужен, когда ход поставлен в очередь — поток закрылся событиемqueued, а ответ появится позже. - Текст ответа — поле
contentпоследнего сообщения ассистента. - Уточнение отправляется тем же запросом в ту же сессию. Контекст сохраняется,
а
clientMessageIdгенерируется заново для каждого сообщения.
bash
#!/usr/bin/env bash
set -euo pipefail
: "${FORGE_URL:?укажите адрес портала}"
: "${FORGE_API_KEY:?укажите личный ключ}"
: "${TASK_TYPE_ID:?укажите taskTypeId из ответа GET /api/tasks/:id}"
AUTH="Authorization: Bearer ${FORGE_API_KEY}"
new_uuid() { uuidgen | tr '[:upper:]' '[:lower:]'; }
# 1. Создание сессии
SESSION_ID=$(curl -sS -X POST "${FORGE_URL}/api/chat-sessions" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{\"taskTypeId\":\"${TASK_TYPE_ID}\"}" \
| jq -r '.data.id')
echo "сессия: ${SESSION_ID}"
# 2. Отправка вопроса. Ответ — поток событий; соединение держится,
# пока портал не завершит ход.
send_message() {
local text="$1"
curl -sS -N --max-time 600 -X POST "${FORGE_URL}/api/chat-sessions/${SESSION_ID}/messages" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{\"text\":\"${text}\",\"attachedFileIds\":[],\"clientMessageId\":\"$(new_uuid)\"}" \
> /dev/null
}
# 3. Ожидание ответа: опрос раз в 3 секунды, пока последнее сообщение
# не станет готовым ответом ассистента.
wait_for_answer() {
while :; do
RESULT=$(curl -sS "${FORGE_URL}/api/chat-sessions/${SESSION_ID}" -H "$AUTH" \
| jq -r '.data.messages[-1] | select(. != null) | "\(.role) \(.status)"')
case "${RESULT}" in
"assistant completed"|"assistant stopped"|"assistant failed") break ;;
esac
sleep 3
done
}
# 4. Печать последнего ответа ассистента
print_answer() {
curl -sS "${FORGE_URL}/api/chat-sessions/${SESSION_ID}" -H "$AUTH" \
| jq -r '[.data.messages[] | select(.role == "assistant")] | last | .content'
}
send_message "Сколько продаж закрыто на этой неделе?"
wait_for_answer
print_answer
# 5. Уточнение в ту же сессию
send_message "Из них сколько закрыто без замечаний?"
wait_for_answer
print_answerPython
import os
import time
import uuid
import requests
FORGE_URL = os.environ["FORGE_URL"].rstrip("/")
HEADERS = {"Authorization": f"Bearer {os.environ['FORGE_API_KEY']}"}
TASK_TYPE_ID = os.environ["TASK_TYPE_ID"] # поле taskTypeId из ответа GET /api/tasks/:id
POLL_SECONDS = 3
TERMINAL = ("completed", "stopped", "failed")
def create_session() -> str:
response = requests.post(
f"{FORGE_URL}/api/chat-sessions",
headers=HEADERS,
json={"taskTypeId": TASK_TYPE_ID},
timeout=30,
)
response.raise_for_status()
return response.json()["data"]["id"]
def send_message(session_id: str, text: str) -> None:
"""Отправляет сообщение и держит соединение, пока портал не завершит ход."""
body = {
"text": text,
"attachedFileIds": [],
"clientMessageId": str(uuid.uuid4()),
}
with requests.post(
f"{FORGE_URL}/api/chat-sessions/{session_id}/messages",
headers=HEADERS,
json=body,
stream=True,
timeout=(10, 600),
) as stream:
for _ in stream.iter_lines():
pass
def wait_for_answer(session_id: str) -> str:
"""Опрашивает сессию, пока последнее сообщение не станет готовым ответом."""
while True:
data = requests.get(
f"{FORGE_URL}/api/chat-sessions/{session_id}", headers=HEADERS, timeout=30
).json()["data"]
messages = data["messages"]
if messages and messages[-1]["role"] == "assistant" and messages[-1]["status"] in TERMINAL:
return messages[-1]["content"]
time.sleep(POLL_SECONDS)
def main() -> None:
session_id = create_session()
print("сессия:", session_id)
send_message(session_id, "Сколько продаж закрыто на этой неделе?")
print(wait_for_answer(session_id))
# Уточнение в ту же сессию
send_message(session_id, "Из них сколько закрыто без замечаний?")
print(wait_for_answer(session_id))
if __name__ == "__main__":
main()Сценарий 3. Ошибки: 401, 403, 429, таймауты
Скрипт по личному ключу сталкивается с тремя кодами отказа. Разбирайте поле
error.message — по нему видно причину.
401 — портал не узнал ключ:
error.message | Причина |
|---|---|
Invalid API key | ключ не передан, передан не в том виде или не найден |
API key has been revoked | ключ отозван — выпустите новый |
API key has expired | наступил срок действия — выпустите новый |
User is not active | учётная запись владельца ключа отключена |
403 — ключ подлинный, но запрос отклонён:
error.message | Причина |
|---|---|
This API key cannot act on behalf of a user | это интеграционный ключ, а не личный |
API keys are not available for admin accounts | ключ выпущен администраторской учётной записью |
API access is disabled for this account | администратор выключил «Доступ к API» |
При 401 и 403 повтор того же запроса без смены ключа не поможет.
429 — превышен лимит. Заголовка Retry-After портал не шлёт, пауза стоит в
тексте сообщения: «Слишком много попыток. Повторите через N минут.»,
«Подождите N сек. перед повторным запуском этой задачи.» или «Превышен лимит
задач (N в час). Попробуйте через M мин.». Скрипт читает число из сообщения и
ждёт; если число не распознано, ждёт 60 секунд.
Таймауты. Ставьте таймаут на каждый запрос. Если портал не ответил за отведённое
время, запрос не завершён. Повторять безопасно запросы GET; повтор POST может
создать задачу или сообщение дважды. Исключение — повтор вопроса в чат с тем же
clientMessageId: портал распознаёт повтор и не создаёт дубля.
bash
#!/usr/bin/env bash
set -euo pipefail
: "${FORGE_URL:?укажите адрес портала}"
: "${FORGE_API_KEY:?укажите личный ключ}"
AUTH="Authorization: Bearer ${FORGE_API_KEY}"
# Пауза перед повтором: число читается из текста сообщения об ошибке.
wait_seconds() {
local msg="$1" n
if n=$(printf '%s' "$msg" | grep -oE 'через [0-9]+ минут' | grep -oE '[0-9]+' | head -1); then
echo $((n * 60))
elif n=$(printf '%s' "$msg" | grep -oE 'через [0-9]+ мин' | grep -oE '[0-9]+' | head -1); then
echo $((n * 60))
elif n=$(printf '%s' "$msg" | grep -oE '[0-9]+ сек' | grep -oE '[0-9]+' | head -1); then
echo "$n"
else
echo 60
fi
}
# Запрос с повтором при 429 и таймаутом 60 секунд.
request() {
local method="$1" url="$2" body="${3:-}"
local attempt=0 response code body_text msg pause
while :; do
attempt=$((attempt + 1))
if [ -n "$body" ]; then
response=$(curl -sS --max-time 60 -w $'\n%{http_code}' -X "$method" \
"$url" -H "$AUTH" -H "Content-Type: application/json" -d "$body")
else
response=$(curl -sS --max-time 60 -w $'\n%{http_code}' -X "$method" \
"$url" -H "$AUTH")
fi
code="${response##*$'\n'}"
body_text="${response%$'\n'*}"
case "$code" in
2*) printf '%s\n' "$body_text"; return 0 ;;
429)
msg=$(printf '%s' "$body_text" | jq -r '.error.message // empty')
pause=$(wait_seconds "$msg")
echo "429, повтор через ${pause} секунд (попытка ${attempt})" >&2
sleep "$pause"
;;
401|403)
echo "код ${code}: $(printf '%s' "$body_text" | jq -r '.error.message')" >&2
return 1
;;
*)
echo "код ${code}: ${body_text}" >&2
return 1
;;
esac
done
}
# Пример: статус задачи с повтором при 429
request GET "${FORGE_URL}/api/tasks/7d4f2a91-3c58-4e6b-9a02-1f8e6c4d2b37" \
| jq -r '.data.status'Python
import os
import re
import sys
import time
import requests
FORGE_URL = os.environ["FORGE_URL"].rstrip("/")
HEADERS = {"Authorization": f"Bearer {os.environ['FORGE_API_KEY']}"}
# Портал не шлёт заголовок Retry-After: пауза стоит в тексте сообщения.
MINUTES_RE = re.compile(r"через (\d+) мин(ут)?")
SECONDS_RE = re.compile(r"(\d+) сек")
def wait_seconds(message: str) -> int:
match = MINUTES_RE.search(message)
if match:
return int(match.group(1)) * 60
match = SECONDS_RE.search(message)
if match:
return int(match.group(1))
return 60
def request(method: str, url: str, **kwargs) -> requests.Response:
"""Выполняет запрос с таймаутом и повторяет его при 429."""
while True:
response = requests.request(method, url, headers=HEADERS, timeout=60, **kwargs)
if response.status_code != 429:
return response
message = ""
try:
message = response.json()["error"]["message"]
except (ValueError, KeyError):
pass
pause = wait_seconds(message)
print(f"429, повтор через {pause} секунд", file=sys.stderr)
time.sleep(pause)
# Пример: статус задачи с повтором при 429
task = request("GET", f"{FORGE_URL}/api/tasks/7d4f2a91-3c58-4e6b-9a02-1f8e6c4d2b37").json()["data"]
print(task["status"])Если документацию читает ИИ-агент
Чек-лист вызовов в порядке, в котором их делает скрипт. Поля запросов — в разделах «Задачи» и «Чаты».
Задача с файлом:
POST /api/tasks— поляtaskTypeSlug,params(JSON-строка),files. Запомнитеdata.id.GET /api/tasks/:id— опрос раз в 5 секунд, покаstatusне станетdone,partialилиfailed.GET /api/tasks/:id/files— список файлов.GET /api/tasks/:id/files/:fileId/download— по одному запросу на каждый файл сdirection: "output".
Чат:
POST /api/chat-sessions— полеtaskTypeId. Запомнитеdata.id.POST /api/chat-sessions/:id/messages— поляtextиclientMessageId(новый UUID на каждое сообщение). Держите соединение открытым, пока портал не завершит ход.GET /api/chat-sessions/:id— опрос раз в 3 секунды, пока последнее сообщение не станетrole: "assistant"с финальным статусом.- Уточнение — повтор шагов 6–7 в ту же сессию.
Правила:
- Ключ — в заголовке
Authorization: Bearer …каждого запроса. 429— пауза в текстеerror.message(«через N минут», «N сек»); заголовкаRetry-Afterнет.401и403— ключ недействителен или не личный; повтор без смены ключа не поможет.- Таймаут на каждый запрос; повторять безопасно только
GET.