Быстрый старт
Ваше приложение не работает с блокчейном: оно создаёт счёт, отправляет клиента на страницу оплаты и по вебхуку включает доступ. Три шага ниже — весь путь от нуля до первой оплаты.
Ключи, продукты и адрес вебхука создаются в кабинете (или через API — см. разделы ниже). Тестовый или боевой режим выбирает сам ключ: sk_test_ или sk_live_. тест
curl -X POST https://api.builtwide.dthreads.am/v1/checkout/sessions \
-H "Authorization: Bearer $SK" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"priceId":"PRICE_ID",
"endUserRef":"company:42",
"payerEmail":"client@example.com",
"metadata":{"userId":"42"}
}'Авторизация
Каждый запрос к /v1 авторизуется заголовком Authorization: Bearer sk_test_….
- Секретный ключ живёт только на сервере. Никогда не отдавайте его в браузер, в мобильное приложение и в публичный репозиторий.
- Отдельного переключателя режима в API нет — тестовый или боевой режим определяет префикс ключа.
- Формат ошибок везде одинаковый:
{"code":"<machine_code>"}. Лишние поля тела молча отбрасываются.
curl https://api.builtwide.dthreads.am/v1/products \
-H "Authorization: Bearer sk_test_…"
# 401 → {"code":"invalid_api_key"}
# 429 → {"code":"rate_limited"}
# 422 → {"code":"validation_error","params":[{"param":"…","message":"…"}]}Продукты и варианты покупки
Продукт — это то, что вы продаёте. Вариант покупки задаёт тип и сумму; в API эта неизменяемая сущность называется Price.
amount— десятичная строка USDC от 0.50 до 50000; вне диапазона — 422 amount_out_of_range.- Созданный вариант покупки изменить нельзя: на него уже могут ссылаться ссылки на оплату.
PATCH /v1/prices/{id}только включает и выключает его (active). Чтобы поменять сумму — создайте новый вариант покупки и выключите старый. Выключенный продукт отключает все свои варианты покупки. type:one_time|credits_pack(нуженcreditsAmount) |subscription(нуженperiodDays).periodDaysпринимает только фиксированный набор значений:7,30,90,365. Любое другое число (например 31) вернёт 422 invalid_period_days.billingPeriod— календарный срок вместо periodDays:month|year. Продление в то же число следующего месяца или года, 31-е прижимается к концу короткого месяца. Оба поля сразу вернут 422 period_conflict.amount— десятичная строка в USD, до 6 знаков после точки. Ответ дополнительно отдаёт amountMicro — целое в микро-USDC.idварианта покупки — этоpriceIdдля сессии оплаты.publicId— для готовой ссылки оплатыhttps://builtwide.dthreads.am/l/{publicId}. Внутренние идентификаторы продукта и варианта покупки имеют формат UUID v4; publicId варианта покупки — price_ и 24 строчных шестнадцатеричных символа.- Ошибки:
404 product_not_found,422 validation_error,422 invalid_period_days,422 amount_out_of_range,422 credits_amount_required(credits_pack без creditsAmount).
curl -X POST https://api.builtwide.dthreads.am/v1/products \
-H "Authorization: Bearer $SK" \
-H "Content-Type: application/json" \
-d '{"name":"Pro access","description":"Monthly customer subscription"}'
# 201 → {"id":"7a3f21c8-9d64-4b0e-8f52-1c7e4a9d0b31","name":"Pro access","description":"Monthly customer subscription",
# "active":true,"livemode":false,"createdAt":"2026-07-25T00:00:00.000Z"}curl -X POST https://api.builtwide.dthreads.am/v1/products/PRODUCT_ID/prices \
-H "Authorization: Bearer $SK" \
-H "Content-Type: application/json" \
-d '{"type":"subscription","amount":"19.99","periodDays":30}'
# 201 → {"id":"16bd0ac5-5891-42fd-86e7-b99479bc18c8","publicId":"price_738d8ecffdf10fba91f60963",
# "productId":"7a3f21c8-9d64-4b0e-8f52-1c7e4a9d0b31","type":"subscription",
# "amount":"19.990000","amountMicro":"19990000","currency":"usdc","chain_id":84532,
# "creditsAmount":null,"periodDays":30,"active":true}
# все цены продукта
curl https://api.builtwide.dthreads.am/v1/products/PRODUCT_ID/prices \
-H "Authorization: Bearer $SK"Сессия оплаты
Создаётся с сервера. В боевом режиме заголовок Idempotency-Key обязателен: повтор того же запроса вернёт тот же счёт, а не создаст второй; без заголовка придёт 400 idempotency_key_required. В тестовом режиме он необязателен, но тогда повтор создаст второй счёт. В ответе — session_id, invoice_id и url; перенаправьте клиента на url.
{
"session_id": "4f70a63a-ba63-4795-9d70-161fb9953708",
"invoice_id": "inv_23b0c60736e92212",
"url": "https://builtwide.dthreads.am/i/inv_23b0c60736e92212"
}Поля тела: priceId (обязательно), endUserRef, payerEmail, successUrl, cancelUrl, metadata (объект до 4 КБ), expiresInSec (ограничивается диапазоном 900…86400). successUrl и cancelUrl — только http/https, без IP-хостов, и должны совпадать с доменом, который вы зарегистрировали в настройках кабинета. Домен повторно проверяется при каждом открытии страницы оплаты: его удаление сразу скрывает ссылки возврата и в уже созданных сессиях. После оплаты successUrl показывается как возврат к продавцу; до оплаты cancelUrl — как выход в магазин без оплаты. В обе ссылки страница оплаты добавляет invoice_id.
Это ваш идентификатор клиента внутри вашего приложения — по нему платформа связывает оплаты, кредиты и подписки одного и того же человека или компании. Это НЕ обязательно почта: подойдут company:42, tg:123, user_9f3 — любая стабильная строка. Если вы передаёте почту, она нормализуется в вид email:<адрес в нижнем регистре>.
Для цен типа subscription и credits_pack endUserRef обязателен — без него запрос вернёт 422 end_user_ref_required. Для one_time он необязателен, но полезен для сверки.
403 receiving_address_not_verified/403 merchant_suspended/403 public_business_name_required/403 public_support_email_required/403 public_refund_policy_required- Публичное название, отдельная почта поддержки и ссылка или текст политики возврата обязательны для приёма оплаты в боевом режиме. Почта для входа не раскрывается; профиль задаётся в разделе Настройки → Данные страницы оплаты.
404 price_not_found409 idempotency_in_progress— тот же ключ ещё обрабатывается, повторите позже422 price_inactive,422 end_user_ref_required,422 idempotency_key_reused422 redirect_origin_not_allowed— successUrl или cancelUrl указывает на домен, которого нет в списке разрешённых. Добавьте домен в кабинете: Настройки → Данные страницы оплаты → Домены возврата после оплаты.
Вебхуки
Вебхуки — основной способ узнать об оплате. Адрес вебхука регистрируется в кабинете, вы получаете секрет whsec_… (показывается один раз). Один адрес получает ВСЕ типы событий своего режима; на режим можно завести до 5 адресов.
{
"id": "evt_…",
"type": "invoice.paid",
"created": 1730000000,
"livemode": false,
"api_version": "2026-07-01",
"data": {
"object": {
"invoice_id": "inv_724742c530d56c27",
"priceId": "…",
"paymentPath": "wallet",
"currency": "usdc",
"chain_id": 84532,
"amountMicro": "49000000",
"feeMicro": "490000",
"livemode": false,
"endUserRef": "email:buyer@example.com",
"payerEmail": "buyer@example.com",
"txHash": "0x…",
"metadata": { "orderId": "A-1" }
}
}
}Со своим заказом событие связывается двумя способами: по invoice_id — тот же идентификатор, что вернула сессия оплаты и который принимает GET /v1/invoices/{id}, — и по metadata, которую вы передали при создании сессии.
Каждое событие по счёту (оплата, недоплата, истечение, отмена и возврат) содержит currency и числовой chain_id. Не выводите валюту и сеть из суммы или режима ключа: используйте эти поля из самого события.
Подпись приходит в заголовке Stablebill-Signature в формате t=<unix>,v1=<hex>. Подписывается строка ${t}.${rawBody} алгоритмом HMAC-SHA256 с ключом whsec_…, где rawBody — сырое тело запроса как есть (не пересериализуйте JSON). При ротации секрета в заголовке может быть несколько v1= — старый и новый; достаточно совпадения с любым.
Для маршрутизации и логов запрос также содержит Stablebill-Event-Id и Stablebill-Event-Type. Они повторяют id и type из тела, но не заменяют проверку Stablebill-Signature по сырому телу.
const crypto = require("crypto");
function verify(req, whsec) {
const header = req.get("Stablebill-Signature") || "";
const parts = Object.fromEntries(header.split(",").map(kv => kv.split("=")));
const t = parts.t;
const raw = req.body; // Buffer из express.raw({ type: "application/json" })
const expected = crypto.createHmac("sha256", whsec)
.update(`${t}.${raw.toString("utf8")}`).digest("hex");
const provided = header.split(",").filter(p => p.startsWith("v1=")).map(p => p.slice(3));
const ok = provided.some(v =>
v.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected)));
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; // защита от повторной отправки
return ok && fresh;
}invoice.paidinvoice.expiredinvoice.refundedinvoice.canceledinvoice.underpaidcredits.low_balancesubscription.activatedsubscription.renewedsubscription.amendedsubscription.expiringsubscription.expiredorphan.detectedbilling.renewal_duebilling.past_duebilling.pausedinvoice.paid— разовая покупка или пополнение оплачены: выдайте продукт или доступ.subscription.activated/subscription.renewed— включите или продлите подписку клиента.subscription.amended— состав подписки изменился: доплата за повышение подтверждена либо продление применило запланированное понижение. Срок не двигается.billing.renewal_due— период тарифа продавца BuiltWide скоро закончится; это не событие подписки покупателя.subscription.expiring— подписка клиента кончается: используйте событие для своего уведомления, если у BuiltWide нет почты покупателя. Приходит за трое суток и ещё раз в последний час — какой именно раз, видно в поле reminder.subscription.expired— снимите доступ.orphan.detected— деньги пришли, но какой счёт они закрывают — непонятно (сумма не совпала, счёт уже истёк или адрес делят два счёта). Заказ оплачен, но не отмечен: разберите приход в кабинете. В теле — сеть, транзакция, индекс лога и адрес, на который пришли деньги.
Доставка «минимум один раз»: до 7 попыток с нарастающими паузами 0 → 30 с → 2 мин → 10 мин → 1 ч → 6 ч → 24 ч. Успехом считается любой ответ 2xx от вашего адреса вебхука.
Дедуплицируйте на своей стороне по идентификатору события (evt_…) — он остаётся тем же при повторных попытках и ручной повторной отправке. Обработали evt_X — второй раз игнорируйте.
Подписки клиентов
Автосписаний нет и быть не может: модель некастодиальная, платформа не имеет доступа к кошельку клиента. Подписка = ручное продление, каждый период клиент платит заново.
- Подписка клиента создаётся после оплаты варианта покупки Price с type=subscription — приходит subscription.activated.
billing.renewal_dueотносится к тарифу самого продавца BuiltWide. Для подписок ваших покупателей приходит subscription.expiring: за трое суток и в последний час. Если endUserRef имеет вид email:адрес, BuiltWide отправляет письмо со ссылкой продления; для других идентификаторов уведомление отправляет ваше приложение. Доставка требует настроенного почтового сервиса.- Продлил → subscription.renewed. Не продлил в срок → subscription.expired. Событие billing.past_due относится к неоплаченному продлению тарифа самого продавца BuiltWide.
curl "https://api.builtwide.dthreads.am/v1/subscriptions?endUserRef=company:42&status=active" \
-H "Authorization: Bearer $SK"
# 200 → {"data":[{"id":"2f9c48d1-0b73-4e6a-9c15-6d8b2e5f7a04","price_id":"16bd0ac5-5891-42fd-86e7-b99479bc18c8","end_user_ref":"company:42",
# "status":"active","expires_at":"2026-08-24T00:00:00.000Z",
# "current_period_start":"2026-07-25T00:00:00.000Z","anchor_day":25}]}
# anchor_day — число месяца, на которое приходится календарный срок; его несут и события
# subscription.activated и subscription.renewed. Продление, оплаченное до конца срока, сохраняет число;
# продление, оплаченное после истечения срока, переносит его на день этой оплаты: новый срок
# начинается тогда, и покупатель получает полный месяц за полную цену. Месячные квоты стройте по этому числу.
curl -X POST https://api.builtwide.dthreads.am/v1/subscriptions/SUBSCRIPTION_ID/renew-link \
-H "Authorization: Bearer $SK"
# 201 → {"url":"https://builtwide.dthreads.am/i/inv_23b0c60736e92212"}
# Остановить продление: оплаченный доступ действует до конца, платёж не возвращается
curl -X POST https://api.builtwide.dthreads.am/v1/subscriptions/SUBSCRIPTION_ID/cancel \
-H "Authorization: Bearer $SK"
# 200 → {"status":"canceled","expires_at":"2026-08-24T09:00:00.000Z",
# "canceled_at":"2026-07-28T12:00:00.000Z"}
# subscription.canceled сообщает, что доступ не продлевается; отзывайте его по subscription.expiredСмена тарифа действующей подписки идёт через расчёт и подтверждение. Расчёт ничего не списывает: он показывает доплату за остаток оплаченного периода (дороже) или дату, с которой начнёт действовать более дешёвый тариф. Подтверждение с Idempotency-Key выставляет счёт ровно на доплату, а состав меняется только после подтверждённой оплаты. Внутри одного интервала срок не двигается — период уже оплачен. Переход между месяцем и годом считается иначе: остаток месяца нельзя дотянуть до года, поэтому покупается новый период целиком, а неиспользованный остаток старого тарифа зачитывается; тогда срок заменяется, и это видно по new_period_end. Понижение оставляет текущий период на старых условиях, и следующая ссылка продления ведёт уже на новую цену.
# 1. Сколько стоит смена. Деньги не двигаются, ответ действует недолго.
curl -X POST https://api.builtwide.dthreads.am/v1/subscriptions/SUBSCRIPTION_ID/amendments/quote \
-H "Authorization: Bearer $SK" -H "Content-Type: application/json" \
-d '{"targetPriceId":"PRICE_ID"}'
# 200 → {"subscription_id":"…","subscription_version":3,"kind":"upgrade",
# "amount":"15.000000","amount_micro":"15000000","currency":"usdc",
# "period_start":"2026-09-01T00:00:00.000Z","period_end":"2026-10-01T00:00:00.000Z",
# "effective_at":"2026-09-16T00:00:00.000Z","quoted_at":"2026-09-16T00:00:00.000Z",
# "quote_expires_at":"2026-09-16T00:15:00.000Z"}
# 2. Подтвердите. Верните версию, сумму и quoted_at: сумма проверяется на момент
# quoted_at, поэтому расчётная цена держится 15 минут; если подписка изменилась, запрос отклоняется.
curl -X POST https://api.builtwide.dthreads.am/v1/subscriptions/SUBSCRIPTION_ID/amendments \
-H "Authorization: Bearer $SK" -H "Idempotency-Key: upgrade-company-42-october" \
-H "Content-Type: application/json" \
-d '{"targetPriceId":"PRICE_ID","expectedVersion":3,"expectedAmountMicro":"15000000","quotedAt":"2026-09-16T00:00:00.000Z"}'
# 201 → {"amendment_id":"sam_…","status":"awaiting_payment","invoice_id":"inv_…",
# "payment_url":"https://builtwide.dthreads.am/i/inv_…"}
# Повтор с тем же Idempotency-Key вернёт ту же смену и тот же счёт.
# Понижение не требует оплаты: status "scheduled", invoice_id null, payment_url null.
# 3. Тариф меняется по subscription.amended, а не только по invoice.paid. Те же данные
# можно прочитать в любой момент — по идентификатору или по ключу, который вы использовали, если ответ потерялся.
curl https://api.builtwide.dthreads.am/v1/subscriptions/SUBSCRIPTION_ID/amendments/sam_… -H "Authorization: Bearer $SK"
curl "https://api.builtwide.dthreads.am/v1/subscriptions/SUBSCRIPTION_ID/amendments?idempotencyKey=upgrade-company-42-october" \
-H "Authorization: Bearer $SK"
# 4. Отмена смены, которая ещё не применена (запланированное понижение, неоплаченная доплата).
curl -X POST https://api.builtwide.dthreads.am/v1/subscriptions/SUBSCRIPTION_ID/amendments/sam_…/cancel \
-H "Authorization: Bearer $SK" -H "Content-Type: application/json" \
-d '{"expectedVersion":4}'
# Возврат доплаты откатывает тариф на прежнюю цену и не трогает конец периода:
# этот период оплачен другим счётом. Недоплаченная доплата ничего не меняет.
# Переход между месяцем и годом считается иначе: остаток месяца нельзя
# растянуть на год, поэтому покупатель платит за новый период целиком, а неиспользованный остаток старого тарифа
# зачитывается. Расчёт показывает обе части, а new_period_end говорит, что период сдвигается:
# {"kind":"upgrade","credit_micro":"9500000","charge_micro":"190000000","amount_micro":"180500000",
# "period_end":"2026-10-01T00:00:00.000Z","new_period_end":"2027-09-16T00:00:00.000Z"}
# Оплата этого счёта заменяет период и переносит день привязки на день оплаты. В обратную сторону
# (с года на месяц) зачёта обычно хватает на более дешёвый тариф, поэтому это понижение,
# которое ждёт конца оплаченного года — за излишек деньги не возвращаются.Сверка платежей
Для сверки с вашей таблицей учёта есть аутентифицированный список счетов и деталка по одному счёту. Оба видят только счета вашего мерчанта и только в режиме ключа (тестовом или боевом).
limit— 1…100, по умолчанию 50.status—pending,confirming,underpaid,paid,expired,canceled,refunded.cursor— идентификатор последнего элемента предыдущей страницы. Ответ списка всегда {data, has_more}: has_more=true значит, что есть ещё страница.- В /v1 суммы приходят парой: amount — десятичная строка, amount_micro — целое в микро-USDC; для арифметики берите amount_micro. В /public поля amount, received и remaining — десятичные строки USDC без суффикса _micro.
price_id— внутренний идентификатор цены, а не publicId из ссылки оплаты /l/{publicId}. Для сопоставления со своей ценой сравнивайте по значению, которое вернул POST /v1/products/{id}/prices в поле id.POST /v1/invoices/{id}/cancelотменяет только счёт вашего мерчанта в ожидании оплаты в режиме текущего ключа API. cancelUrl сам по себе статус не меняет: это безопасная ссылка возврата покупателя, а решение закрыть заказ остаётся за вашим сервером.
Если входящий USDC-перевод не удалось автоматически сопоставить со счётом, он появится в кабинете в разделе Платежи → Переводы без счёта в том же режиме (тестовом или боевом). Владелец или сотрудник поддержки может проверить хеш транзакции, отправителя и сумму, затем доказуемо привязать перевод к конкретному счёту (invoice_id) или закрыть запись вручную с обязательным объяснением. Привязка переводит счёт в статус «оплачен» и запускает обычные вебхуки, кредиты, подписку и квитанцию.
curl "https://api.builtwide.dthreads.am/v1/invoices?limit=50&status=paid" \
-H "Authorization: Bearer $SK"
# 200 → {"data":[{"id":"inv_…","status":"paid","amount":"19.990000",
# "amount_micro":"19990000","received":"19.990000","overpaid":null,
# "currency":"usdc","chain_id":84532,"price_id":"16bd0ac5-5891-42fd-86e7-b99479bc18c8",
# "end_user_ref":"company:42","payer_email":null,"payment_path":"wallet",
# "tx_hash":"0x…","paid_at":"2026-07-25T00:00:00.000Z",
# "created_at":"2026-07-25T00:00:00.000Z","livemode":false}],
# "has_more":false}
# следующая страница: cursor = идентификатор последнего элемента предыдущей
curl "https://api.builtwide.dthreads.am/v1/invoices?limit=50&cursor=inv_…" -H "Authorization: Bearer $SK"
curl https://api.builtwide.dthreads.am/v1/invoices/INVOICE_ID -H "Authorization: Bearer $SK"
# 404 → {"code":"invoice_not_found"}# Публичный метод, без ключа: его опрашивает страница плательщика в ожидании.
curl https://api.builtwide.dthreads.am/public/invoices/INVOICE_ID/status
# 200 → {"status":"pending","received":"0.000000","remaining":"49.000000","history":[]}Оплата с биржи
Покупателю не нужен подключённый кошелёк: на странице оплаты он выбирает оплату с биржи и получает адрес счёта, сеть Base, токен USDC и ровную сумму. Деньги приходят на этот адрес, откуда контракт пересылает их на ваш подтверждённый кошелёк — платформа их не хранит и перенаправить не может.
- Обычно ваш сервер эту публичную ручку не вызывает: достаточно перенаправить клиента на url сессии оплаты. Она приведена здесь для собственного клиента страницы оплаты и отладки.
- Покупатель отправляет сумму счёта на deposit_address в сети Base до expires_at. Адрес определяет принадлежность перевода, а статус paid зависит от суммы подтверждённых поступлений. Частичные переводы складываются; до достижения порога счёт остаётся underpaid. Допуск недоплаты = min(2, max(0.30, сумма × 1%)) USDC. Например, счёт 5 USDC засчитывается от 4.70 (недоплата 6%), а 49 USDC — от 48.51. Допуск уменьшает фактическую выручку продавца; лишняя сумма и поздний перевод обрабатываются отдельно.
- Минимальный счёт — 5 USDC. Меньшая сумма вернёт 422 amount_below_exchange_min. Оплата с биржи в боевом режиме работает всегда: первые 500 USDC оборота без комиссии, дальше с каждого платежа удерживается процент. Тариф BuiltWide убирает этот процент. Карта входит в тариф BuiltWide и доступна только когда /public/onramp/availability показывает, что она включена.
- Повторная активация идемпотентно возвращает те же реквизиты. После подтверждения платёж имеет payment_path=exchange и вызывает обычный invoice.paid.
curl -X POST https://api.builtwide.dthreads.am/public/invoices/INVOICE_ID/exchange-mode
# 200 → {"pay_to":"0x…","amount":"49.000000","deposit_address":"0x…",
# "network":"base","token":"USDC","expires_at":1784985600}Проверка в тестовом режиме
Чтобы проверить всю интеграцию без реального USDC, создайте сессию оплаты ключом sk_test_, сохраните invoice_id и завершите счёт публичным тестовым методом. Счёт станет оплаченным и запустит те же вебхуки и продуктовые последствия, что реальная оплата.
- Сначала включите приём вебхуков в тестовом режиме и дедупликацию по evt_…, затем вызовите simulate и дождитесь invoice.paid. Для credits_pack также проверьте баланс, для subscription — subscription.activated.
- Счёт боевого режима нельзя симулировать: API вернёт 403 test_payment_live_forbidden. Истёкший или отменённый тестовый счёт вернёт 409 test_payment_unavailable. Повтор уже оплаченного тестового счёта безопасен.
# INVOICE_ID — это invoice_id, который вернула сессия оплаты с ключом sk_test_
curl -X POST https://api.builtwide.dthreads.am/public/invoices/INVOICE_ID/simulate
# 200 → {"paid":true,"status":"paid"}
# Отправляются обычные вебхуки invoice.paid, кредитов и подписки. Их последствия — баланс кредитов,
# подписка — появляются чуть позже, когда фоновый обработчик разберёт событие; опрашивайте или дождитесь
# вебхука, а не читайте сразу после этого вызова.Возвраты
Возврат — это подтверждаемый перевод USDC из вашего текущего подтверждённого кошелька получения. Начало возврата сразу отзывает выданные кредиты или сокращает подписку, но счёт станет refunded и событие invoice.refunded придёт только после проверки перевода в блокчейне.
- Передайте непустой reason и сохраните refund_id. Повтор по тому же счёту вернёт 409 refund_already_exists вместе с существующим refund_id.
- Для платежа с кошелька ответ сразу содержит адрес исходного плательщика. Для платежа с биржи никогда не возвращайте на общий горячий кошелёк биржи: отправьте покупателю одноразовый address_collection_url, получите его адрес и дождитесь статуса awaiting_transfer.
- Отправьте ровно amount_usdc в сети Base с подтверждённого кошелька получения и передайте tx_hash. Статус confirming можно опрашивать через GET; завершение подтверждает invoice.refunded. Списки возвращают {data, has_more}.
- В тестовом режиме выполните то же подтверждение, затем POST …/simulate: средства не двигаются, но полный жизненный цикл и событие завершаются. Возврат в боевом режиме симулировать нельзя.
Если первый ответ подтверждения потерян, повторите запрос и возьмите существующий refund_id из 409 refund_already_exists. Потерянную или истёкшую ссылку выбора адреса перевыпускайте через POST /v1/refunds/{refundId}/regenerate-link: старая ссылка сразу перестаёт работать, новая выдаётся только до фиксации адреса покупателя. Если почта покупателя известна, ссылка также отправляется письмом.
Начало возврата и выплата — разные состояния. До invoice.refunded опрашивайте GET /v1/refunds/{refundId} и сверяйте права через баланс кредитов или список подписок. Внешнее приложение должно учитывать отзыв прав уже после одобрения возврата. Публичный API не предлагает отмену или отклонение возврата; длительный pending, failed или escalated требует разбора через поддержку. Не возвращайте доступ и не отправляйте деньги повторно только по тайм-ауту запроса.
curl -X POST https://api.builtwide.dthreads.am/v1/invoices/INVOICE_ID/refund \
-H "Authorization: Bearer $SK" \
-H "Content-Type: application/json" \
-d '{"reason":"Customer request"}'
# Одобрение возврата сразу отзывает то, что оплатил счёт: конец подписки
# пересчитывается по периодам, которые ещё оплачены и не возвращены, а кредиты,
# выданные по счёту, списываются. Если перевод отклонён или возврат отменён, всё возвращается.
# Сохраните refund_id и следуйте инструкции, которую вернёт API.
curl https://api.builtwide.dthreads.am/v1/refunds/REFUND_ID -H "Authorization: Bearer $SK"
# После отправки точной суммы возврата в USDC с вашего подтверждённого кошелька получения:
curl -X POST https://api.builtwide.dthreads.am/v1/refunds/REFUND_ID/transaction \
-H "Authorization: Bearer $SK" \
-H "Content-Type: application/json" \
-d '{"tx_hash":"0x…64 hex characters…"}'
# Только тестовая среда: пройти тот же жизненный цикл без движения средств.
curl -X POST https://api.builtwide.dthreads.am/v1/refunds/REFUND_ID/simulate \
-H "Authorization: Bearer sk_test_…"
# Счёт несёт свой возврат: GET /v1/invoices/INVOICE_ID возвращает refund.completed_at —
# null, пока перевод в ожидании, и заполнено после подтверждения в блокчейне.Кредиты
Кредиты — ваши целочисленные единицы потребления, привязанные к endUserRef; это не USDC. Оплата цены credits_pack начисляет их автоматически. Ручные выдача и списание нужны для корректировок и потребления внутри вашего приложения.
- grant и consume принимают положительное целое amount и обязательно требуют уникальный Idempotency-Key. Повтор того же запроса безопасен; тот же ключ с другим телом вернёт 422 idempotency_key_reused.
- consume никогда не уводит баланс ниже нуля: при нехватке ответ — 402 insufficient_credits. Используйте возвращённый balance как подтверждённый новый баланс.
- Баланс и журнал требуют тот же нормализованный endUserRef. Журнал листается через startingAfter и возвращает {data, has_more}; limit — 1…100.
- Событие credits.low_balance предупреждает о настроенном пороге и может содержать top_up_offer с публичным идентификатором цены выбранного пакета; остаток и историю всё равно сверяйте через API.
curl -X POST https://api.builtwide.dthreads.am/v1/credits/grant \
-H "Authorization: Bearer $SK" \
-H "Idempotency-Key: grant-order-0143" \
-H "Content-Type: application/json" \
-d '{"endUserRef":"customer-0143","amount":1000}'
# 201 → {"balance":1000}
curl -X POST https://api.builtwide.dthreads.am/v1/credits/consume \
-H "Authorization: Bearer $SK" \
-H "Idempotency-Key: consume-request-0143" \
-H "Content-Type: application/json" \
-d '{"endUserRef":"customer-0143","amount":25}'
# 200 → {"balance":975}
curl "https://api.builtwide.dthreads.am/v1/credits/balance?endUserRef=customer-0143" \
-H "Authorization: Bearer $SK"
curl "https://api.builtwide.dthreads.am/v1/credits/ledger?endUserRef=customer-0143&limit=20" \
-H "Authorization: Bearer $SK"
# 200 → {"data":[{"id":"cled_…","delta":1000,"reason":"manual",…}],"has_more":false}
# reason: manual — выдали или списали вручную этим API; purchase — начислено по оплате пакета.
# Продолжайте с startingAfter=cled_… из последней строки; идентификаторы непрозрачны.Выгрузка платежей
CSV-выгрузка передаёт платежи вашего мерчанта потоком только в режиме ключа API. Она подходит для бухгалтерской сверки и не требует загружать весь набор в память.
- from и to — необязательные ISO-даты, обе границы включаются. Без них диапазон — от начала данных до текущего момента; неверная дата вернёт 422 invalid_date.
- Ответ — text/csv, вложенный файл payments.csv. Колонка currency содержит код валюты записи в нижнем регистре, как API; amount, fee и net — нейтральные денежные поля с 6 знаками; payment_path различает оплату с кошелька (wallet), с биржи (exchange) и картой (card).
curl --fail --output payments.csv \ "https://api.builtwide.dthreads.am/v1/exports/payments.csv?from=2026-07-01T00:00:00.000Z&to=2026-07-31T23:59:59.999Z" \ -H "Authorization: Bearer $SK" # invoice_id,created_at,paid_at,product,price,currency,amount,fee, # net,payment_path,tx_hash,payer_email,end_user_ref,status
Ручная подпись кошельком
Обычный браузерный кошелёк можно подключить и подписать прямо в кабинете. Ручной путь нужен только для холодного кошелька, аппаратного устройства или мультисиг-интерфейса.
Нулевой адрес и известные адреса платформы/USDC отклоняются до создания запроса на подпись. Если в сети Base по адресу есть код контракта, кабинет покажет отдельное предупреждение и потребует явного подтверждения. Продолжайте только для своего кошелька-контракта с EIP-1271; никогда не используйте адрес контракта токена или чужого приложения. Если узел сети Base недоступен, запрос на подпись не создаётся — безопасно повторите позже.
- Введите адрес получения при первоначальной настройке или в Настройках и запросите сообщение для подписи.
- Нажмите «Копировать». Не перепечатывайте сообщение: все переносы строк входят в подпись.
- Для обычного кошелька подпишите точную строку тем же адресом через EIP-191 personal_sign. Для кошелька-контракта создайте подпись в его мультисиг-интерфейсе: сервер проверит её через EIP-1271 в Base. Это подпись сообщения, не перевод.
- Вставьте полную подпись в шестнадцатеричном виде с префиксом 0x и нажмите «Проверить подпись». Для смены существующего адреса повторите это для текущего кошелька и подтвердите текущим паролем. У подтверждения есть отдельный лимит 5 попыток в минуту на IP; при rate_limited подождите до следующего окна.
Полный справочник API
Полная спецификация всех ручек, тел запросов и кодов ошибок живёт в Swagger. Машинная версия — тот же контракт в формате OpenAPI JSON: скормите его генератору клиента или своему AI-агенту.