Reseller API Documentation | FollowTurk
FollowTurk giveaway has started! --:--:-- Join now
View Cart 0 Login Register

Language & Currency

Currency

Reseller API

Buy products with your account balance and pull delivered accounts, codes and links — and the state of pending deliveries — into your own system automatically.

Base URLhttps://followturk.com/api/v1
Contents

Getting started

  1. Verify your phone number. Your account must be at least 14 days old.
  2. In Account → API press "Create my key" and enter the code sent to your phone. The key is shown only once — store it safely. You get an e-mail whenever a key is created.
  3. Add balance. The API buys only with balance; if your balance is not enough, no order is created.

Tiers and limits

Limits rise automatically with your account's total purchases. See your current tier in GET /account and on Account → API.

TierConditionDaily spendingRequests / minute
1 Total $0 purchases, 14-day-old account $25 30
2 Total $100 purchases, 30-day-old account, 1 paid order(s) $150 60
3 Total $500 purchases, 60-day-old account, 1 paid order(s) $500 120

The daily limit and spent_today count the full amount of every API order placed today: a refunded order still counts and a refund does not reopen the limit. On a product priced below a cent this amount can differ from total by up to half a cent per order.

If you need higher limits, contact our support team. Accounts with a card dispute (chargeback) permanently lose API access.

Authentication

Send the key only in a request header:

curl -H "Authorization: Bearer ftk_YOUR_KEY" https://followturk.com/api/v1/account

curl -H "X-API-Key: ftk_YOUR_KEY" https://followturk.com/api/v1/account
Requests with the key in the URL are refused. The API is server-to-server only: never put the key in web page, mobile app or browser code.

Endpoints

MethodEndpointDescription
GET/accountBalance, daily limit and today's spend
GET/categoriesCategories that have API products
GET/productsProducts sold through the API (paged)
GET/products/{id}One product
POST/ordersPlace an order paid from balance
GET/ordersOrders you placed through the API (paged, newest first, without delivery details)
GET/orders/{order_number}Order and line status, delivered accounts/codes/links
POST/orders/{order_number}/lines/{line}/inputGive new input to a line that waits for it (e.g. a new invite link)
POST/orders/{order_number}/lines/{line}/steam-guardSteam: a Steam Guard code for the line's account

/products and /orders accept the query parameters page and per_page (max 100; default 50 for /products, 20 for /orders). /products also accepts category_id and type (e.g. discord_boost). Each takes a single value; an array (e.g. page[]=1) is refused with 422 invalid_query.

Account

GET/account

Your balance, tier and limits. tier is 1, 2 or 3; it is "custom" when your limits were set for you separately. Amounts are in USD.

{ "success": true, "data": { "username": "...", "balance": 42.5, "currency": "USD", "tier": 1,
  "daily_limit": 25.0, "spent_today": 4.5, "remaining_today": 20.5, "requests_per_minute": 30 } }

Categories

GET/categories

Only categories that have API products are listed. Use id as category_id on /products. This endpoint is not paged.

{ "success": true, "data": [{ "id": 5, "name": "...", "product_count": 12 }] }

Products

GET/products
GET/products/{id}
{ "id": 26, "name": "...", "type": "discord_boost", "category": { "id": 5, "name": "..." }, "currency": "USD",
  "unit_price": 0.282, "min_quantity": 2, "max_quantity": 14, "in_stock": true,
  "delivery_mode": "async", "quantity_step": 2, "order_minimum": 0.5,
  "required_fields": [{ "name": "invite_link", "type": "string", "required": true, "max_length": 512, "rule": "...", "example": "https://discord.gg/abcd1234" }],
  "options": [] }

{ "id": 123, "name": "...", "type": "code", "category": { "id": 7, "name": "..." }, "currency": "USD",
  "price": null, "variants": [{ "id": 789, "name": "...", "unit_price": 4.5, "min_quantity": 1, "max_quantity": 50, "in_stock": true }], "in_stock": true,
  "delivery_mode": "instant", "quantity_step": 1, "order_minimum": 1, "required_fields": [], "options": [] }
FieldDescription
id, name, category, currencyProduct id, name, category (id, name) and currency
unit_priceUnit price. A product with variants has price = null and variants instead; a product priced by a choice has null here and pricing instead. It may be null for a product that cannot be bought.
min_quantity, max_quantityThe smallest and largest quantity allowed on a line
quantity_stepThe quantity must be a multiple of this (Discord boosts: 2, others: 1)
variants[]Each variant: id (variant_id on the order), name, unit_price, min_quantity, max_quantity, in_stock
pricingThe price table of an account product priced by a choice
in_stockWhether the product can be bought right now (yes/no)
delivery_modeinstant: delivered with the order; async: poll the order
order_minimumThe smallest order amount when the product is bought alone (USD)
required_fields[]The input asked in items[].fields: name, type, required, max_length, rule, example. When there is one: pattern and required_when (e.g. {"delivery": "boost"}: the field is required only with this option)
options[]The choices given in items[].options
descriptionOnly in GET /products/{id}: a short description

Product types and buyer input

Every product carries type, delivery_mode, required_fields and options. The price is the same as on the website. Buyer input, delivery and refunds are too, with three differences: on Steam you send your end customer's customer_ref; an MM2 item is delivered directly as a code; and when a code cannot be handed over, the website keeps the amount and the team delivers the code later, while the API returns the amount to your balance at once. For quantity limits and available options, min_quantity, max_quantity and options in the product reply apply.

typeWhatDeliveryfields
accountAccount — some products are priced by a choice (options.selection + options.amount)instant; if only part of the quantity is ready the line is partial and that part is in delivery, it becomes delivered when the rest arrives; if none is ready yet it is processing. When the product's delivery_mode is async the line stays processing until it is ready—
codeDigital codeinstant—
discord_membersDiscord members — a one-time link is delivereda few minutes (async)—
discord_boostDiscord server boosts — quantity in multiples of 2asyncinvite_link
robuxRbx — through an RBL gamepassasync (within 24 hours)gamepass_link
steam_shared_accountShared Steam library account — per game, quantity 1asynccustomer_ref
mm2_itemRBL MM2 item — delivered as a code, redeemed at followturk.deliveryasync—
  • invite_link: Discord server invite (discord.gg/<code>, discord.com/invite/<code> or the bare code). It must never expire and have no use limit. An invalid or expired invite is refused with 422 field_invalid.
  • gamepass_link: An RBL gamepass link priced at EXACTLY the ordered quantity, on sale, regional pricing off (or the link of a public game that has such a gamepass). A bare username is not accepted. A link that does not fit is refused with 422 field_invalid before any money moves.
  • options.delivery: Only when the product lists it in options: boost (default, needs invite_link) or code (a boost code is delivered; do not send invite_link). An option that is not listed is refused with 422 unknown_option.
  • customer_ref: Steam: your own id for the end customer (1-64 letters, digits, "_" or "-"). Always send the same value for the same person: that person gets one account per game and cannot buy the same game twice (409 already_owned). Upper and lower case differ: Ali and ali are two people.
  • options.selection + options.amount: Account products priced by a choice: selection = skins, be or level (those listed in the product's options), amount = a whole number inside that selection's range (sent as a string). The price table is in the product's pricing field; the exact amount is computed on the order.
  • in_stock: Whether the product can be bought right now (yes/no). No quantity is given.

order_minimum is the smallest order amount when the product is bought alone (0.50 USD for Discord products). Rbx quantity goes up to the product's max_quantity; at most 5 Rbx lines per order.

Place an order

POST/orders

Send a unique Idempotency-Key header with every order (8-64 chars: letters, digits, "-" or "_"). Resending with the same key never creates a second order; it returns the first one, so retrying after a network error is safe.

curl -X POST https://followturk.com/api/v1/orders \
  -H "Authorization: Bearer ftk_YOUR_KEY" \
  -H "Idempotency-Key: order-20261012-0001" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"product_id":123,"quantity":2},{"product_id":456,"variant_id":789,"quantity":1}]}'
FieldDescription
items[].product_idProduct ID (integer, required)
items[].quantityQuantity (integer, within the product's min/max)
items[].variant_idRequired for products with variants, not allowed otherwise
items[].fieldsThe buyer input listed in the product's required_fields (object of strings). Any field not on that list is refused.
items[].optionsChoices listed in the product's options (e.g. DC Boost: delivery = boost | code).
items[].max_unit_priceOptional. If the unit price is above it the order is refused with 409 price_changed and nothing is charged (for products whose price changes often).
Prices are always computed by the server; requests that send a price, coupon or any other field are refused. The amount leaves your balance in the same step that creates the order. Up to 10 lines (codes: at most 2 lines and 50 codes a line; Steam: at most 3 lines); the minimum order amount is the same as on the website.
One order holds one product family: accounts and codes together, or only Discord members, or only Discord boosts, or only Rbx, or only Steam accounts, or only MM2 items. A mixed order is refused with 422 mixed_order.
Checking the buyer input (Discord, RBL) can take a few seconds; keep your client timeout at 100 seconds or more. If you get no answer (timeout, 500, 502, 503 or 524), resend with the same Idempotency-Key and the same body: the order is created once and charged once.
curl -X POST https://followturk.com/api/v1/orders \
  -H "Authorization: Bearer ftk_YOUR_KEY" \
  -H "Idempotency-Key: boost-20261103-0001" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"product_id":26,"quantity":4,"fields":{"invite_link":"https://discord.gg/abcd1234"}}]}'

Reply

A new order answers 201. Resent with the same Idempotency-Key the answer is 200 with meta.replayed = true. The fields are explained under Read an order.

HTTP 201
{ "success": true, "data": { "order_number": "FT1A2B3C4D", "status": "completed", "total": 9.5, "currency": "USD", "created_at": "2026-11-05T12:00:00+03:00",
  "items": [{ "line": 1, "product_id": 123, "name": "...", "type": "code", "quantity": 2, "unit_price": 4.75, "total": 9.5, "status": "delivered" }] } }

HTTP 200
{ "success": true, "data": { ... }, "meta": { "replayed": true } }

Delivered accounts and codes are not in the create response; fetch them with GET /orders/{order_number}.

Read an order

GET/orders
GET/orders/{order_number}

Delivery details come only in GET /orders/{order_number}. Reading accounts and codes this way counts as taking delivery, just like on the website.

{ "success": true, "data": { "order_number": "FT1A2B3C4D", "status": "completed", "total": 9.5, "currency": "USD", "created_at": "2026-11-05T12:00:00+03:00",
  "items": [{ "line": 1, "product_id": 123, "name": "...", "type": "code", "quantity": 2, "unit_price": 4.75, "total": 9.5, "status": "delivered", "delivery": ["AAAA-BBBB", "CCCC-DDDD"] }] } }

total is what really left your balance, the same amount as your transaction record. On a product priced below a cent this amount can change by 1 cent with your balance, so always keep your accounts with the total in the reply. On an order with several lines each line total is rounded on its own and they can add up to a different amount; the order's total is the amount that counts.

Line fields

FieldDescription
linePosition, starting at 1; used on the /lines/{line} endpoints
product_id, name, type, quantityProduct id, name, type and quantity
unit_price, totalUnit price and line total
statusThe line's status (see the table below)
deliveryWhat was delivered; only in GET /orders/{order_number}
refundWhen refunded, the amount: refund.amount
progressDiscord boosts: sending progress
input_requiredWhat a needs_input line asks for
customer_refSteam: the value you sent
selectionAn account product priced by a choice: type (skins, be, level) and amount

Line status

async lines move on within minutes; poll the order at a sensible interval (e.g. once a minute).

statusMeaning
processingQueued or being delivered
needs_inputDelivery needs new input: send the input_required.fields to /lines/{line}/input
in_reviewOur team is checking it; nothing to do on your side
deliveredDelivered (see delivery)
partialPartly delivered: the delivered part is in delivery. The rest either arrives (the line becomes delivered) or its price is refunded (refund.amount)
failedCould not be delivered; contact support
refundedThe whole line was refunded: the amount really went back to your balance
cancelledCancelled. Without refund.amount the amount did not go back to your balance; contact support
on_holdThe order is on hold for review
{ "line": 1, "type": "discord_boost", "quantity": 4, "status": "needs_input",
  "input_required": { "fields": ["invite_link"], "actions": [], "reason": "invite_invalid", "screening_ack_required": false } }

{ "line": 1, "type": "account", "quantity": 2, "status": "delivered", "delivery": ["login:password", "login:password"] }
{ "line": 1, "type": "account", "quantity": 5, "status": "partial", "delivery": ["login:password", "login:password"] }
{ "line": 1, "type": "code", "quantity": 3, "status": "partial", "refund": { "amount": 2.5 }, "delivery": ["AAAA-BBBB", "CCCC-DDDD"] }
{ "line": 1, "type": "discord_members", "status": "delivered", "delivery": { "link": "https://…" } }
{ "line": 1, "type": "discord_boost", "status": "delivered", "progress": { "delivered": 4, "target": 4 } }
{ "line": 1, "type": "discord_boost", "status": "delivered", "delivery": { "code": "...", "boosts": 4, "period": "1m", "expires_at": "..." } }
{ "line": 1, "type": "robux", "status": "delivered", "delivery": { "robux": 1000 } }
{ "line": 1, "type": "steam_shared_account", "status": "delivered", "customer_ref": "shop-9921", "delivery": { "login": "...", "password": "...", "guard": { "available": true, "method": "mafile" } } }
{ "line": 1, "type": "mm2_item", "status": "delivered", "delivery": { "quantity": 2, "codes": [{ "code": "...", "quantity": 2, "name": "..." }] } }

The shape of delivery follows the line's type: accounts and codes = a list of texts; Discord members = link; Discord boost = code (or only progress); Rbx = robux; Steam = login, password, guard; MM2 = codes. When a delivery was handed over as plain text, delivery is a list of texts on any type (Steam, Discord members and MM2 too); on Rbx and MM2 such texts can also come in a details field. progress appears only once sending has started.

A partial refund (refund.amount) does not hide delivered accounts, codes or links, and the line keeps its delivery status. Delivery details are hidden only when the whole line or the whole order is refunded.

Order status

Order status: processing (work is going on for at least one line), needs_input (at least one line waits for input), completed (nothing more will happen; read the lines, some may be refunded), refunded (the whole amount went back to your balance), cancelled (if the amount did not come back, contact support), failed (no line could be delivered; contact support) or on_hold. You can stop polling on completed, refunded, cancelled and failed. on_hold is temporary: the order can go on after the review, so keep polling.

If the key that placed the order was rotated or deleted: for orders under 7 days old the new key receives the delivery details. For older orders the line status is shown but delivery is not, an MM2 item is not turned into a code, and Steam Guard answers 409 not_available_for_this_key. You reach those deliveries from your account on the website.

Send input

POST/orders/{order_number}/lines/{line}/input

Works only on a line with status = needs_input and takes no money. It is the same action as "fix the link / try again" on the website's order page, with the same limits.

curl -X POST https://followturk.com/api/v1/orders/FT1A2B3C4D/lines/1/input \
  -H "Authorization: Bearer ftk_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"fields":{"invite_link":"https://discord.gg/newcode"}}'

-d '{"action":"retry","screening_ack":true}'
-d '{"fields":{"gamepass_link":"https://…/game-pass/123456789/My-Pass"}}'
FieldDescription
fieldsNew values for the line's input_required.fields
action"retry": try again with the same input (after fixing the server setting)
screening_ackDC Boost: confirms you turned off the server's "Apply to Join" (required when screening_ack_required = true)

The reply returns the updated order without delivery details; meta.line is the line that was handled. DC Boost: 5 attempts per line and 20 per account per hour; Rbx: 15 checks and 3 sends per 10 minutes.

While ordering through the API is closed, this endpoint is closed too (503 orders_disabled or orders_paused).

MM2 items

An MM2 line is delivered directly as codes; no extra call is needed. After the order the line is processing; when the codes are ready it becomes delivered and the codes are in delivery.codes of GET /orders/{order_number} (code, quantity, name). The codes are redeemed at followturk.delivery; hand the code to your end customer.

{ "line": 1, "type": "mm2_item", "quantity": 2, "status": "processing" }

{ "line": 1, "type": "mm2_item", "quantity": 2, "status": "delivered",
  "delivery": { "quantity": 2, "codes": [{ "code": "...", "quantity": 2, "name": "..." }] } }
  • Reading the order again returns the same codes; it makes no new code and takes no money.
  • The code is created the first time you read the order after the line is delivered (GET /orders or GET /orders/{order_number}). If you took the item into the game from your account on the website before that, there is no code for it: delivery.state = withdrawn_on_site.
  • If the order cannot be placed in this quantity you get 409 out_of_stock; the reply does not say which line or how many. For a product that is on sale, these refusals for a quantity that does not fit are limited to 5 per hour per account: past that the same attempt gets 429 rate_limited, and the Retry-After header and details.retry_after say how many seconds to wait. An order in a quantity that can be bought is not affected by this limit. A refusal for a product the catalogue shows as in_stock: false is not counted.
  • If an item cannot be prepared for a long time the line becomes in_review; our team looks at it and refunds it if needed (refunded). The codes are no longer shown on a refunded line.

Steam Guard

POST/orders/{order_number}/lines/{line}/steam-guard

Gives a Steam Guard code for the account of a delivered Steam line; takes no money. The allowance is per line, as on the website: the first code opens a session in which a set number of codes can be taken, then the allowance locks (details.unlock_at). A failed request costs nothing. Your account also has a daily ceiling (delivered Steam lines × the per-line allowance); when it is reached you get 429 guard_daily_limit with details.unlock_at. The body must be empty, {} or []; anything else is refused with 422 invalid_body and costs nothing.

curl -X POST https://followturk.com/api/v1/orders/FT1A2B3C4D/lines/1/steam-guard \
  -H "Authorization: Bearer ftk_YOUR_KEY" -H "Content-Type: application/json" -d '{}'

{ "success": true, "data": { "line": 1, "code": "AB12C", "method": "mafile", "expires_in": 30,
  "quota": { "used": 1, "max": 7, "remaining": 6, "unlock_at": "..." } } }

The account (login, password and, when needed, ea) is read live on every GET /orders/{order_number}; a changed password shows the new one. If it cannot be read for a moment, delivery = {"state": "temporarily_unavailable"}.

Reply format and error codes

{ "success": true, "data": { ... }, "meta": { "page": 1, "per_page": 50, "total": 120, "total_pages": 3 } }

{ "success": false, "error": { "code": "insufficient_balance", "message": "Insufficient balance.", "details": { "required": 12.5, "balance": 4 } } }
HTTPCodeMeaning
400key_in_url idempotency_key_required Key in URL, or Idempotency-Key missing
401unauthorized Missing or invalid key
402insufficient_balance funds_on_hold Not enough balance, or the part of it added by card recently cannot be spent through the API yet (details.on_hold, details.hold_hours) — no order was created
403account_restricted phone_not_verified not_eligible api_blocked forbidden This account cannot use the API, does not meet the requirements yet, its API access was closed, or requests from this address are refused
404not_found product_not_found order_not_found line_not_found Not found
404wrong_host The request went to a host other than followturk.com/api/v1
405method_not_allowed Only GET and POST are supported
409out_of_stock idempotency_conflict The product is not available right now or not in this quantity, or the Idempotency-Key was used for a different order
409not_needs_input invites_paused The line is not waiting for input, or invites are paused on the server
409already_owned price_changed This customer_ref already owns the game (details.order_number), or the unit price is above max_unit_price
409not_available not_delivered wrong_line_type not_available_for_this_key The line is refunded, cancelled or on hold, not delivered yet, the endpoint does not fit the line, or the order was placed with an earlier key
409guard_login_first guard_unavailable access_ended Steam Guard: sign in to Steam with the account first / no code for this account / access has ended
413body_too_large The request body is too large
422invalid_* product_unavailable below_minimum Invalid request
422unknown_fields variant_required variant_not_allowed duplicate_item The body has a field that is not allowed, no variant was chosen, this product has no variants, or the same product/variant (Steam: the same game and customer_ref) is on two lines
422mixed_order The order mixes product families
422field_required field_invalid unknown_field Buyer input missing, invalid or not used by this product (details.field, details.reason)
422unknown_option option_invalid screening_ack_required Invalid option, or an acknowledgement is required
422option_required A required option is missing (e.g. selection)
429rate_limited daily_limit_reached too_many_failed_attempts A request limit (MM2: refused attempts for an unavailable quantity are limited too) or the daily spending limit is reached, or too many invalid keys were tried from this address. On the per-minute request limit, the invalid key limit and the MM2 limit the Retry-After header gives the wait
429quota_locked guard_cooldown guard_daily_limit busy Steam Guard: the line's allowance is used up (details.unlock_at), asked too often, the account's daily Steam Guard ceiling is reached (details.unlock_at), or another request for the same line is running (busy)
500internal_error Server error. On an order, resend with the same Idempotency-Key and the same body: the order is created once and charged once
503api_disabled orders_disabled orders_paused busy API or ordering temporarily closed or busy; retry later. An account's orders are processed one at a time: if its turn does not come within 5 seconds the answer is busy, nothing is charged, retry with the same Idempotency-Key. When present, the Retry-After header gives the wait

API key

Applications and key management live in Account → API.

Log in

About FollowTurk

Ask an AI

Ask the leading AI assistants about FollowTurk, our products and how trustworthy we are.

Select Currency

Language Detected!