Item Purchase API
Allow players to purchase in-game items using their existing currency balances. This API provides a secure, transactional method for managing your in-game economy.
Note on minor accounts: item purchases are intentionally not gated by guardian approval, even when the buyer is flagged as a minor. The guardian's approval was given when the currency entered the wallet (via a guardian-approved transfer or send); a second gate at spend time is redundant friction. Minor users can purchase items normally. See Guardian Approval for the full scope.
Purchase Item
The core endpoint to execute an item purchase.
POST /api/item-purchases/purchase-itemPurchase History
Retrieve a specific player's item purchase history.
GET /api/item-purchases/player-purchase-historyOrder Details
Get detailed financial records for a specific order.
GET /api/item-purchases/order-details1. Purchase Item Endpoint
/api/item-purchases/purchase-item
Securely processes an item purchase by deducting from a player's balance.
Security Warning
This endpoint must only be called from your secure game server. Your server should first validate the player's session and then make this API call. Never call it directly from a game client.
Authentication Required
All requests to this endpoint must include your game's secret key in the request headers:
X-Game-Secret-Key: your_game_secret_key_hereYou can find your game's secret key in your Invo dashboard under Game Settings → API Keys.
Request Body Parameters
| Parameter | Type | Description |
|---|---|---|
| client_request_id | string | A unique ID generated by your client to ensure idempotency. See section below. |
| player_email | string | The email address of the purchasing player. |
| player_name | string | The player's current display name. |
| item_id | string | Your internal unique identifier for the item. |
| item_name | string | The display name of the item. |
| item_quantity | integer | The number of units being purchased. |
| unit_price | string | The price of a single unit of the item, as a string. |
| total_price | string | The total price for the purchase (quantity * unit_price). |
| item_category | string (optional) | A category for the item (e.g., "Weapons", "Skins"). |
| item_description | string (optional) | Free-form description for receipts and history views. |
| player_phone | string (optional) | E.164 phone number; recorded if the player auto-creates on this call. |
Successful response (HTTP 200)
{
"status": "success",
"message": "Item purchased successfully",
"transaction_id": "txn_...",
"order_id": "ord_...",
"purchase_details": {
"item_id": "legendary_sword_001",
"item_name": "Legendary Sword",
"quantity": 1,
"total_price": "100.00",
"currency_name": "Gold Coins"
},
"balance_info": {
"previous_balance": "1000.00",
"amount_spent": "100.00",
"new_balance": "900.00"
},
"financial_breakdown": {
"total_paid": "100.00",
"developer_revenue": "90.00",
"platform_fee": "10.00"
}
}new_balance is authoritative for the moment of write — treat it as eventually-consistent for batch reads. financial_breakdown reflects the network's current item-purchase fee split (90% to you, 10% network fee).
2. Idempotency and Safe Retries
Preventing Duplicate Purchases
Network errors can happen. To prevent a player from being charged twice for the same item if a request is retried, you must provide a unique client_request_id for every purchase attempt.
If our server receives a request with a client_request_id it has already processed, it will not create a new transaction. Instead, it will return a 409 Conflict error, letting you know the purchase is already complete. A good practice is to generate a UUID on your client for each new purchase action.
3. Code Implementation Examples
UnityUnity C# (Server-Side Logic)
// This function MUST be on your secure server.
public IEnumerator PurchaseItem(string playerEmail, string itemName, string itemId, int quantity, float price, Action<string> onSuccess, Action<string> onError)
{
string url = "https://invo.network/api/item-purchases/purchase-item";
var requestData = new {
client_request_id = System.Guid.NewGuid().ToString(), // Generate a unique ID for each attempt
player_email = playerEmail,
player_name = "Player fetched from your DB",
item_id = itemId,
item_name = itemName,
item_quantity = quantity,
unit_price = price.ToString("F2"),
total_price = (price * quantity).ToString("F2")
};
string jsonBody = JsonUtility.ToJson(requestData);
using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
{
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("X-Game-Secret-Key", "your_game_secret_key_here");
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success) {
onSuccess?.Invoke(request.downloadHandler.text);
} else {
onError?.Invoke(request.downloadHandler.text);
}
}
}UnrealUnreal C++ (Server-Side Logic)
// This function MUST be on your secure server.
void AYourGameBackend::PurchaseItem(const FString& PlayerEmail, const FString& ItemId, int32 Quantity, float Price)
{
TSharedPtr<FJsonObject> RequestObj = MakeShareable(new FJsonObject);
RequestObj->SetStringField("client_request_id", FGuid::NewGuid().ToString());
RequestObj->SetStringField("player_email", PlayerEmail);
RequestObj->SetStringField("player_name", "Player Name from DB");
RequestObj->SetStringField("item_id", ItemId);
RequestObj->SetStringField("item_name", "Item Name from DB");
RequestObj->SetNumberField("item_quantity", Quantity);
RequestObj->SetStringField("unit_price", FString::Printf(TEXT("%.2f"), Price));
RequestObj->SetStringField("total_price", FString::Printf(TEXT("%.2f"), Price * Quantity));
FString RequestBody;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&RequestBody);
FJsonSerializer::Serialize(RequestObj.ToSharedRef(), Writer);
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL("https://invo.network/api/item-purchases/purchase-item");
Request->SetVerb("POST");
Request->SetHeader("X-Game-Secret-Key", "your_game_secret_key_here");
Request->SetHeader("Content-Type", "application/json");
Request->SetContentAsString(RequestBody);
Request->OnProcessRequestComplete().BindUObject(this, &AYourGameBackend::OnPurchaseItemResponse);
Request->ProcessRequest();
}GodotGodot GDScript (Server-Side Logic)
# This function MUST be on your secure server.
# Assumes you have an HTTPRequest node named 'http_request'.
# You can use OS.execute("uuidgen") on supported platforms or other methods for unique IDs.
func purchase_item(player_email: String, item_id: String, quantity: int, price: float):
var url = "https://invo.network/api/item-purchases/purchase-item"
var headers = [
"X-Game-Secret-Key: your_game_secret_key_here",
"Content-Type: application/json"
]
var body = {
"client_request_id": UUID.new().uuid_string(), # Requires a UUID plugin or custom function
"player_email": player_email,
"player_name": "Player From DB",
"item_id": item_id,
"item_name": "Item From DB",
"item_quantity": quantity,
"unit_price": "%.2f" % price,
"total_price": "%.2f" % (price * quantity)
}
http_request.request(url, headers, HTTPClient.METHOD_POST, JSON.stringify(body))
4. Helper Endpoints
/api/item-purchases/player-purchase-history
Retrieves a paginated list of items a player has purchased.
Query Parameters: ?player_email=player@example.com&limit=25&offset=0
Example Response:
{
"player": { /* player details */ },
"item_purchase_history": [
{
"transaction_id": "txn_...",
"status": "completed",
"amount": "150.00",
"created_at": "2024-06-21T14:30:00Z",
"item_id": "legendary_sword_001",
"item_name": "Legendary Sword",
"item_quantity": 1
}
],
"pagination": {
"total_count": 1,
"limit": 25,
"offset": 0,
"has_more": false
}
}/api/item-purchases/order-details
Retrieves the complete financial record for a single purchase order.
Query Parameters: ?order_id=ord_... OR ?transaction_id=txn_...
Example Response:
{
"order": { /* ... full order object ... */ },
"financial_summary": {
"game_currency_spent": "150.00",
"payment_method": "in_game_currency"
},
"status_timeline": {
"created_at": "2024-06-21T14:30:00Z",
"completed_at": "2024-06-21T14:30:00Z"
}
}5. Advanced Security Features
Automated Fraud & Spam Protection
The API includes several automated security checks to protect your game's economy from abuse. If a player triggers one of these checks, they will receive a 429 Too Many Requests error with a message explaining the block.
- Insufficient-balance lockout: 50 failed purchase attempts due to insufficient funds within 5 minutes triggers a 5-minute cooldown.
- Rapid-same-item lockout: More than 10 purchase attempts for the same item within 1 minute triggers a temporary block.
- Per-player spam lockout: More than 200 total purchase attempts within 5 minutes triggers a 5-minute cooldown.
- All 429 responses include a
retry_afterfield (seconds) — back off accordingly.