Где хранить ключ, если вы не программист
Базовый рецепт. Форма записи на сайте, связка с таблицей, бот — всё строится поверх него. Сначала находим ключу правильное место.
Почему ключ нельзя вставить в сайт
Ключ — это доступ ко всему вашему кабинету. По нему можно выгрузить список встреч с именами, почтой и телефонами клиентов и отменить любую запись. Пароль вы никому не показываете — ключ ровно того же уровня.
Код, вставленный в конструктор сайта, виден любому посетителю: правая кнопка мыши, «Исходный код страницы» — и ключ у него. Поэтому Планёрка и не принимает обращения напрямую из браузера: даже если вы вставите ключ на страницу, ничего не заработает.
Ключу нужно место, где он лежит закрыто, а наружу выдаётся только результат. Такое место называется прослойкой. Соберём её на Google Apps Script — это бесплатный сервис, он идёт вместе с обычным Google-аккаунтом. Ни сервера, ни хостинга, ни карты не нужно.
- 01Ключ
- 02Скрипт
- 03Ключ внутрь
- 04Публикация
- 05Проверка
Создать ключ
Откройте Ключи API и создайте ключ. Дайте ему понятную метку, например «Форма на сайте»: если однажды придётся его отозвать, вы будете знать, что именно сломается.
Ключ показывается один раз
Скопируйте его целиком, вместе с началом cal_. Если закрыли окно и не сохранили — просто создайте новый, старый отзовите.
Создать прослойку
Откройте script.google.com и нажмите «Новый проект». Всё, что уже написано в файле, удалите и вставьте код ниже целиком. Разбираться в нём не нужно — он вставляется один раз и больше не меняется.
var API = "https://planerka.app/rest/v2";
function key() {
return PropertiesService.getScriptProperties().getProperty("PLANERKA_API_KEY");
}
function call(method, path, version, body) {
return UrlFetchApp.fetch(API + path, {
method: method,
contentType: "application/json",
headers: { Authorization: "Bearer " + key(), "cal-api-version": version },
payload: body ? JSON.stringify(body) : undefined,
muteHttpExceptions: true
});
}
function json(obj) {
return ContentService.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
function doGet(e) {
var p = e.parameter || {};
// ?action=types — открытые типы встреч владельца ключа
if (p.action === "types") {
var rt = call("GET", "/event-types", "2024-08-13");
var st = JSON.parse(rt.getContentText());
if (rt.getResponseCode() !== 200) return json({ error: st.error || st });
var data = st.data || {};
var list = [];
Object.keys(data).forEach(function (k) {
var t = data[k];
if (t && !t.hidden) {
list.push({
id: t.id,
title: t.title,
slug: t.slug,
description: t.description || "",
minutes: t.lengthInMinutes || t.length,
price: t.price || 0,
currency: t.currency || "RUB"
});
}
});
return json(list);
}
// свободное время на один день: ?eventTypeId=…&date=2026-07-30&timeZone=…
var id = Number(p.eventTypeId);
if (!id) return json({ error: { code: "NO_EVENT_TYPE", message: "нужен eventTypeId" } });
var tz = p.timeZone || "Europe/Moscow";
var start = p.date || Utilities.formatDate(new Date(), tz, "yyyy-MM-dd");
var end = Utilities.formatDate(
new Date(new Date(start + "T00:00:00Z").getTime() + 86400000), "UTC", "yyyy-MM-dd"
);
var cache = CacheService.getScriptCache();
var ck = "slots|" + id + "|" + start + "|" + tz;
var hit = cache.get(ck);
if (hit) return ContentService.createTextOutput(hit).setMimeType(ContentService.MimeType.JSON);
var url = "/slots?eventTypeId=" + id + "&start=" + start + "&end=" + end +
"&timeZone=" + encodeURIComponent(tz);
var r = call("GET", url, "2024-09-04");
var s = JSON.parse(r.getContentText());
if (r.getResponseCode() !== 200) return json({ error: s.error || s });
var out = JSON.stringify(s.data || {});
cache.put(ck, out, 60);
return ContentService.createTextOutput(out).setMimeType(ContentService.MimeType.JSON);
}
function doPost(e) {
var b = JSON.parse(e.postData.contents);
if (b.trap) return json({ ok: true });
if (!b.eventTypeId || !b.start || !b.name || !b.email) {
return json({ ok: false, error: { code: "BAD_REQUEST" } });
}
var r = call("POST", "/bookings", "2024-08-13", {
eventTypeId: Number(b.eventTypeId),
start: b.start,
attendee: {
name: b.name,
email: b.email,
phoneNumber: b.phone || null,
timeZone: b.timeZone || "Europe/Moscow",
language: "ru"
},
metadata: { source: "site" }
});
var code = r.getResponseCode();
if (code === 201) return json({ ok: true, data: JSON.parse(r.getContentText()).data });
if (code === 409) return json({ ok: false, reason: "taken" });
return json({ ok: false, error: JSON.parse(r.getContentText()) });
}Сохраните файл
Нажмите значок дискеты или Ctrl+S. Публикуется всегда последняя сохранённая версия, а не то, что вы видите на экране.
Положить ключ внутрь
Слева нажмите шестерёнку «Настройки проекта», пролистайте до «Свойства скрипта» и добавьте одно свойство.
Именно здесь ключ и будет жить. В самом коде его нет, поэтому код можно кому угодно показывать и пересылать.
Опубликовать
Кнопка «Развернуть» в правом верхнем углу → «Новое развёртывание» → тип «Веб-приложение». Google попросит разрешение — согласитесь.
- Тип
- Веб-приложение
- Запуск от имени
- Меня
- Доступ
- Все
Скопируйте полученную ссылку — она заканчивается на /exec. Это и есть адрес вашей прослойки, дальше он понадобится везде.
Проверить
Откройте в браузере ту же ссылку, дописав в конец ?action=types:
https://script.google.com/macros/s/…/exec?action=typesДолжен появиться список ваших открытых типов встреч:
[{"id":42165,"title":"Консультация","minutes":50,"price":0,"currency":"RUB"}]Получилось — готово. Ключ лежит в надёжном месте, наружу торчит безопасная ссылка.
Что умеет прослойка
Три обращения — из них собирается любая форма записи.
Время запрашивается по одной дате, а не на месяц вперёд: длинный диапазон Планёрка отдаёт заметно дольше. Ответ на каждую дату держится в памяти минуту, поэтому повторный заход бесплатный.
Если что-то пошло не так
Откройте ссылку с ?action=types и смотрите, что она отвечает.
Две ловушки, на которых теряют больше всего времени
Код не сохранён. Публикуется последняя сохранённая версия. Всегда нажимайте Ctrl+S перед развёртыванием.
«Новое развёртывание» выдаёт новую ссылку, а старая продолжает работать по прежнему коду. Если правки «не применились» — вы почти наверняка проверяете старый адрес. Чтобы сохранить прежний, идите в «Управление развёртываниями», нажмите карандаш у активного и выберите «Версия: Новая».
А вот менять сам ключ можно свободно: свойства читаются в момент обращения, переразвёртывание для этого не нужно.
Правила обращения с ключом
- На каждую задачу — свой ключ со своей меткой. Тогда отзыв одного не ломает остальные.
- Ключ живёт только в свойствах скрипта. Не в коде, не в переписке, не в заметках.
- Отдавать ключ исполнителю можно, но после сдачи работы отзовите его и создайте новый.
- Если ключ где-то засветился — отзовите его в разделе «Ключи API», создайте новый и впишите в свойства. Минутное дело.
- Не заставляйте прослойку пересылать наружу список встреч: там контакты всех ваших клиентов. Она должна отдавать только типы встреч, свободное время и подтверждение записи.