curl --request POST \
--url https://api.suby.fi/api/subscription/create \
--header 'Content-Type: application/json' \
--header 'X-Suby-Api-Key: <api-key>' \
--data '
{
"productId": "pro_sub456",
"customerId": "usr_abc123",
"customerEmail": "customer@example.com",
"customerFirstName": "John",
"customerLastName": "Doe",
"priceCents": "999",
"discountCode": "WELCOME10",
"externalRef": "sub_ref_001",
"metadata": {},
"customFields": [
{
"key": "discord_username",
"label": "Discord username",
"required": false,
"placeholder": "e.g. test#1234",
"options": [
{
"value": "twitter",
"label": "Twitter / X"
}
],
"validation": {
"regex": "^.{2,32}$",
"errorMessage": "Please enter a valid Discord username"
}
}
],
"successUrl": "<string>",
"cancelUrl": "<string>",
"expiresAt": "2026-08-01T12:00:00.000Z"
}
'import requests
url = "https://api.suby.fi/api/subscription/create"
payload = {
"productId": "pro_sub456",
"customerId": "usr_abc123",
"customerEmail": "customer@example.com",
"customerFirstName": "John",
"customerLastName": "Doe",
"priceCents": "999",
"discountCode": "WELCOME10",
"externalRef": "sub_ref_001",
"metadata": {},
"customFields": [
{
"key": "discord_username",
"label": "Discord username",
"required": False,
"placeholder": "e.g. test#1234",
"options": [
{
"value": "twitter",
"label": "Twitter / X"
}
],
"validation": {
"regex": "^.{2,32}$",
"errorMessage": "Please enter a valid Discord username"
}
}
],
"successUrl": "<string>",
"cancelUrl": "<string>",
"expiresAt": "2026-08-01T12:00:00.000Z"
}
headers = {
"X-Suby-Api-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Suby-Api-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
productId: 'pro_sub456',
customerId: 'usr_abc123',
customerEmail: 'customer@example.com',
customerFirstName: 'John',
customerLastName: 'Doe',
priceCents: '999',
discountCode: 'WELCOME10',
externalRef: 'sub_ref_001',
metadata: {},
customFields: [
{
key: 'discord_username',
label: 'Discord username',
required: false,
placeholder: 'e.g. test#1234',
options: [{value: 'twitter', label: 'Twitter / X'}],
validation: {regex: '^.{2,32}$', errorMessage: 'Please enter a valid Discord username'}
}
],
successUrl: '<string>',
cancelUrl: '<string>',
expiresAt: '2026-08-01T12:00:00.000Z'
})
};
fetch('https://api.suby.fi/api/subscription/create', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.suby.fi/api/subscription/create",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'productId' => 'pro_sub456',
'customerId' => 'usr_abc123',
'customerEmail' => 'customer@example.com',
'customerFirstName' => 'John',
'customerLastName' => 'Doe',
'priceCents' => '999',
'discountCode' => 'WELCOME10',
'externalRef' => 'sub_ref_001',
'metadata' => [
],
'customFields' => [
[
'key' => 'discord_username',
'label' => 'Discord username',
'required' => false,
'placeholder' => 'e.g. test#1234',
'options' => [
[
'value' => 'twitter',
'label' => 'Twitter / X'
]
],
'validation' => [
'regex' => '^.{2,32}$',
'errorMessage' => 'Please enter a valid Discord username'
]
]
],
'successUrl' => '<string>',
'cancelUrl' => '<string>',
'expiresAt' => '2026-08-01T12:00:00.000Z'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Suby-Api-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.suby.fi/api/subscription/create"
payload := strings.NewReader("{\n \"productId\": \"pro_sub456\",\n \"customerId\": \"usr_abc123\",\n \"customerEmail\": \"customer@example.com\",\n \"customerFirstName\": \"John\",\n \"customerLastName\": \"Doe\",\n \"priceCents\": \"999\",\n \"discountCode\": \"WELCOME10\",\n \"externalRef\": \"sub_ref_001\",\n \"metadata\": {},\n \"customFields\": [\n {\n \"key\": \"discord_username\",\n \"label\": \"Discord username\",\n \"required\": false,\n \"placeholder\": \"e.g. test#1234\",\n \"options\": [\n {\n \"value\": \"twitter\",\n \"label\": \"Twitter / X\"\n }\n ],\n \"validation\": {\n \"regex\": \"^.{2,32}$\",\n \"errorMessage\": \"Please enter a valid Discord username\"\n }\n }\n ],\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"expiresAt\": \"2026-08-01T12:00:00.000Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Suby-Api-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.suby.fi/api/subscription/create")
.header("X-Suby-Api-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"productId\": \"pro_sub456\",\n \"customerId\": \"usr_abc123\",\n \"customerEmail\": \"customer@example.com\",\n \"customerFirstName\": \"John\",\n \"customerLastName\": \"Doe\",\n \"priceCents\": \"999\",\n \"discountCode\": \"WELCOME10\",\n \"externalRef\": \"sub_ref_001\",\n \"metadata\": {},\n \"customFields\": [\n {\n \"key\": \"discord_username\",\n \"label\": \"Discord username\",\n \"required\": false,\n \"placeholder\": \"e.g. test#1234\",\n \"options\": [\n {\n \"value\": \"twitter\",\n \"label\": \"Twitter / X\"\n }\n ],\n \"validation\": {\n \"regex\": \"^.{2,32}$\",\n \"errorMessage\": \"Please enter a valid Discord username\"\n }\n }\n ],\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"expiresAt\": \"2026-08-01T12:00:00.000Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.suby.fi/api/subscription/create")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Suby-Api-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"productId\": \"pro_sub456\",\n \"customerId\": \"usr_abc123\",\n \"customerEmail\": \"customer@example.com\",\n \"customerFirstName\": \"John\",\n \"customerLastName\": \"Doe\",\n \"priceCents\": \"999\",\n \"discountCode\": \"WELCOME10\",\n \"externalRef\": \"sub_ref_001\",\n \"metadata\": {},\n \"customFields\": [\n {\n \"key\": \"discord_username\",\n \"label\": \"Discord username\",\n \"required\": false,\n \"placeholder\": \"e.g. test#1234\",\n \"options\": [\n {\n \"value\": \"twitter\",\n \"label\": \"Twitter / X\"\n }\n ],\n \"validation\": {\n \"regex\": \"^.{2,32}$\",\n \"errorMessage\": \"Please enter a valid Discord username\"\n }\n }\n ],\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"expiresAt\": \"2026-08-01T12:00:00.000Z\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"paymentId": "pay_sub789",
"paymentUrl": "https://checkout.suby.fi/sub/pay_sub789",
"expiresAt": "2023-11-07T05:31:56Z",
"metadata": {},
"customFields": [
{
"key": "discord_username",
"label": "Discord username",
"type": "input",
"required": false,
"placeholder": "e.g. test#1234",
"options": [
{
"value": "twitter",
"label": "Twitter / X"
}
],
"validation": {
"regex": "^.{2,32}$",
"errorMessage": "Please enter a valid Discord username"
}
}
]
}
}{
"success": false,
"error": {
"code": "PRODUCT_NOT_FOUND",
"message": "Product not found"
}
}{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
}{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Resource not found"
}
}Create a subscription payment
Creates a subscription payment for a recurring product (frequencyInDays is set).
Returns a checkout URL to redirect your customer to.
For custom price products (isCustomPrice: true), provide priceCents and currency.
One-time products? Use
POST /api/payment/createinstead.
curl --request POST \
--url https://api.suby.fi/api/subscription/create \
--header 'Content-Type: application/json' \
--header 'X-Suby-Api-Key: <api-key>' \
--data '
{
"productId": "pro_sub456",
"customerId": "usr_abc123",
"customerEmail": "customer@example.com",
"customerFirstName": "John",
"customerLastName": "Doe",
"priceCents": "999",
"discountCode": "WELCOME10",
"externalRef": "sub_ref_001",
"metadata": {},
"customFields": [
{
"key": "discord_username",
"label": "Discord username",
"required": false,
"placeholder": "e.g. test#1234",
"options": [
{
"value": "twitter",
"label": "Twitter / X"
}
],
"validation": {
"regex": "^.{2,32}$",
"errorMessage": "Please enter a valid Discord username"
}
}
],
"successUrl": "<string>",
"cancelUrl": "<string>",
"expiresAt": "2026-08-01T12:00:00.000Z"
}
'import requests
url = "https://api.suby.fi/api/subscription/create"
payload = {
"productId": "pro_sub456",
"customerId": "usr_abc123",
"customerEmail": "customer@example.com",
"customerFirstName": "John",
"customerLastName": "Doe",
"priceCents": "999",
"discountCode": "WELCOME10",
"externalRef": "sub_ref_001",
"metadata": {},
"customFields": [
{
"key": "discord_username",
"label": "Discord username",
"required": False,
"placeholder": "e.g. test#1234",
"options": [
{
"value": "twitter",
"label": "Twitter / X"
}
],
"validation": {
"regex": "^.{2,32}$",
"errorMessage": "Please enter a valid Discord username"
}
}
],
"successUrl": "<string>",
"cancelUrl": "<string>",
"expiresAt": "2026-08-01T12:00:00.000Z"
}
headers = {
"X-Suby-Api-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Suby-Api-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
productId: 'pro_sub456',
customerId: 'usr_abc123',
customerEmail: 'customer@example.com',
customerFirstName: 'John',
customerLastName: 'Doe',
priceCents: '999',
discountCode: 'WELCOME10',
externalRef: 'sub_ref_001',
metadata: {},
customFields: [
{
key: 'discord_username',
label: 'Discord username',
required: false,
placeholder: 'e.g. test#1234',
options: [{value: 'twitter', label: 'Twitter / X'}],
validation: {regex: '^.{2,32}$', errorMessage: 'Please enter a valid Discord username'}
}
],
successUrl: '<string>',
cancelUrl: '<string>',
expiresAt: '2026-08-01T12:00:00.000Z'
})
};
fetch('https://api.suby.fi/api/subscription/create', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.suby.fi/api/subscription/create",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'productId' => 'pro_sub456',
'customerId' => 'usr_abc123',
'customerEmail' => 'customer@example.com',
'customerFirstName' => 'John',
'customerLastName' => 'Doe',
'priceCents' => '999',
'discountCode' => 'WELCOME10',
'externalRef' => 'sub_ref_001',
'metadata' => [
],
'customFields' => [
[
'key' => 'discord_username',
'label' => 'Discord username',
'required' => false,
'placeholder' => 'e.g. test#1234',
'options' => [
[
'value' => 'twitter',
'label' => 'Twitter / X'
]
],
'validation' => [
'regex' => '^.{2,32}$',
'errorMessage' => 'Please enter a valid Discord username'
]
]
],
'successUrl' => '<string>',
'cancelUrl' => '<string>',
'expiresAt' => '2026-08-01T12:00:00.000Z'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-Suby-Api-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.suby.fi/api/subscription/create"
payload := strings.NewReader("{\n \"productId\": \"pro_sub456\",\n \"customerId\": \"usr_abc123\",\n \"customerEmail\": \"customer@example.com\",\n \"customerFirstName\": \"John\",\n \"customerLastName\": \"Doe\",\n \"priceCents\": \"999\",\n \"discountCode\": \"WELCOME10\",\n \"externalRef\": \"sub_ref_001\",\n \"metadata\": {},\n \"customFields\": [\n {\n \"key\": \"discord_username\",\n \"label\": \"Discord username\",\n \"required\": false,\n \"placeholder\": \"e.g. test#1234\",\n \"options\": [\n {\n \"value\": \"twitter\",\n \"label\": \"Twitter / X\"\n }\n ],\n \"validation\": {\n \"regex\": \"^.{2,32}$\",\n \"errorMessage\": \"Please enter a valid Discord username\"\n }\n }\n ],\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"expiresAt\": \"2026-08-01T12:00:00.000Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Suby-Api-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.suby.fi/api/subscription/create")
.header("X-Suby-Api-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"productId\": \"pro_sub456\",\n \"customerId\": \"usr_abc123\",\n \"customerEmail\": \"customer@example.com\",\n \"customerFirstName\": \"John\",\n \"customerLastName\": \"Doe\",\n \"priceCents\": \"999\",\n \"discountCode\": \"WELCOME10\",\n \"externalRef\": \"sub_ref_001\",\n \"metadata\": {},\n \"customFields\": [\n {\n \"key\": \"discord_username\",\n \"label\": \"Discord username\",\n \"required\": false,\n \"placeholder\": \"e.g. test#1234\",\n \"options\": [\n {\n \"value\": \"twitter\",\n \"label\": \"Twitter / X\"\n }\n ],\n \"validation\": {\n \"regex\": \"^.{2,32}$\",\n \"errorMessage\": \"Please enter a valid Discord username\"\n }\n }\n ],\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"expiresAt\": \"2026-08-01T12:00:00.000Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.suby.fi/api/subscription/create")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Suby-Api-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"productId\": \"pro_sub456\",\n \"customerId\": \"usr_abc123\",\n \"customerEmail\": \"customer@example.com\",\n \"customerFirstName\": \"John\",\n \"customerLastName\": \"Doe\",\n \"priceCents\": \"999\",\n \"discountCode\": \"WELCOME10\",\n \"externalRef\": \"sub_ref_001\",\n \"metadata\": {},\n \"customFields\": [\n {\n \"key\": \"discord_username\",\n \"label\": \"Discord username\",\n \"required\": false,\n \"placeholder\": \"e.g. test#1234\",\n \"options\": [\n {\n \"value\": \"twitter\",\n \"label\": \"Twitter / X\"\n }\n ],\n \"validation\": {\n \"regex\": \"^.{2,32}$\",\n \"errorMessage\": \"Please enter a valid Discord username\"\n }\n }\n ],\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"expiresAt\": \"2026-08-01T12:00:00.000Z\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"paymentId": "pay_sub789",
"paymentUrl": "https://checkout.suby.fi/sub/pay_sub789",
"expiresAt": "2023-11-07T05:31:56Z",
"metadata": {},
"customFields": [
{
"key": "discord_username",
"label": "Discord username",
"type": "input",
"required": false,
"placeholder": "e.g. test#1234",
"options": [
{
"value": "twitter",
"label": "Twitter / X"
}
],
"validation": {
"regex": "^.{2,32}$",
"errorMessage": "Please enter a valid Discord username"
}
}
]
}
}{
"success": false,
"error": {
"code": "PRODUCT_NOT_FOUND",
"message": "Product not found"
}
}{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
}{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Resource not found"
}
}Authorizations
API key authentication
Body
ID of a subscription product
"pro_sub456"
Optional. Links the subscription to an existing customer by their stable id (as returned by
GET /api/customer and on payment responses/webhooks). Keeps the same customer linked even if
their email has changed. Takes precedence over customerEmail. Returns 404 if unknown.
255"usr_abc123"
Optional. If provided (and no customerId is given), a user account is created (or reused by
email) immediately and linked to the payment. If both are omitted, the payment is created without
a customer; the email is collected on the hosted checkout page.
"customer@example.com"
Optional. Customer first name. Backfills the display name when the customer has none. Ignored when neither customerId nor customerEmail is provided.
100"John"
Optional. Customer last name. Backfills the display name when the customer has none. Ignored when neither customerId nor customerEmail is provided.
100"Doe"
Price in cents as a string.
Required when the product has isCustomPrice: true. Must NOT be provided for fixed-price products.
This price is locked for all future renewals.
"999"
Currency for the price. Required when priceCents is provided, ignored otherwise.
USD, EUR Optional. A discount code (created via POST /api/discount/create) to pre-apply to this
subscription checkout. Applied to the amount the customer pays. Ignored if the code is invalid,
expired, exhausted, or not attached to this product.
50"WELCOME10"
Your internal reference for this subscription (e.g. an end-user account or wallet id). It is
the subscription-ownership key: reusing the same externalRef for the same customer +
product renews the existing subscription, while a different externalRef creates a
separate subscription that is billed and renewed independently. Omit it to place the
subscription in the shared "no external reference" bucket for that customer + product.
Returned on the subscription object and on every subscription webhook.
255"sub_ref_001"
Extra fields shown on the checkout page to collect information from the customer
(e.g. Discord username, referral source, terms acceptance).
Fields are collected on the initial checkout only — renewal payments do not
re-prompt the customer. The initial payment webhook carries the responses in
context.customFieldsResponse; renewal webhooks have it set to null.
Maximum 10 fields per subscription.
10Show child attributes
Show child attributes
Optional. Hard deadline for the initial checkout, in the future. Once passed, the payment link stops loading and no payment attempt can revive it. Applies to the initial checkout only — it has no effect on renewals once the subscription is active.
Omit for no deadline. To kill a checkout early, use POST /api/payment/{paymentId}/cancel.
"2026-08-01T12:00:00.000Z"
Was this page helpful?

