Available Transfer Destinations API

Get the list of games that players can transfer currency to from your game. This endpoint should be called once daily and cached locally for optimal performance.

Performance Critical - Cache This Response!

✅ DO THIS

  • • Call this API once per day
  • • Cache response locally for 24 hours
  • • Use cached data to build transfer UI
  • • Refresh on app startup or manual refresh

❌ DON'T DO THIS

  • • Call this API every time player opens transfer UI
  • • Make requests during gameplay
  • • Skip caching for "real-time" data
  • • Store response only in memory
POST

/api/transfers/available-destinations

Get games that accept transfers from your game

Authentication Required

All requests must include your game's secret key in the request headers:

X-Game-Secret-Key: your_game_secret_key_here

Request Body Parameters

ParameterTypeRequiredDescription
source_game_idstringYesYour game's ID (must match authenticated game)

Transfer Logic Modes

Universal Transfers

universal_transfers = "yes"
  • Returns: ALL live games that accept incoming transfers
  • Excludes: Your own game, testing games, inactive games
  • Best for: Maximum ecosystem connectivity
  • Configuration: Set in your game's database record

Linked Transfers

universal_transfers = "no"
  • Returns: ONLY games in your linked_game_ids field
  • Excludes: Testing games, inactive games
  • Best for: Curated partnerships, controlled ecosystem
  • Configuration: Manage linked_game_ids array in database

Request Example

POST /api/transfers/available-destinations
Content-Type: application/json
X-Game-Secret-Key: your_game_secret_key_here

{
  "source_game_id": "123456789012"
}

Response Examples

Universal Transfers - Success Response (200 OK)

{
  "status": "success",
  "source_game_id": "123456789012",
  "source_game_name": "Adventure Quest",
  "universal_transfers": "yes",
  "transfer_mode": "universal",
  "available_games": [
    {
      "game_id": "987654321098",
      "game_name": "Space Warriors",
      "developer_name": "Stellar Studios",
      "publisher_name": "Galaxy Entertainment",
      "genre": "Action",
      "platform": "Mobile",
      "game_status": "live",
      "game_icon": "https://cdn.invo.network/games/space_icon.png",
      "game_poster": "https://cdn.invo.network/games/space_poster.jpg",
      "game_url": "https://spacewarriors.com",
      "game_description": "Epic space combat game",
      "currency_name": "Energy Credits",
      "currency_symbol": "EC",
      "currency_symbol_url": "https://cdn.invo.network/currencies/ec_icon.png",
      "minimum_transfer": "5.00",
      "maximum_transfer": "1000.00"
    },
    {
      "game_id": "111222333444",
      "game_name": "Fantasy Kingdom",
      "developer_name": "Magic Studios",
      "publisher_name": null,
      "genre": "RPG",
      "platform": "Web",
      "game_status": "live",
      "game_icon": "https://cdn.invo.network/games/fantasy_icon.png",
      "game_poster": null,
      "game_url": "https://fantasykingdom.com",
      "game_description": "Build your magical empire",
      "currency_name": "Gold Coins",
      "currency_symbol": "GC",
      "currency_symbol_url": null,
      "minimum_transfer": "1.00",
      "maximum_transfer": null
    }
  ],
  "total_destinations": 2
}

Linked Transfers - Success Response (200 OK)

{
  "status": "success",
  "source_game_id": "123456789012",
  "source_game_name": "Adventure Quest",
  "universal_transfers": "no",
  "transfer_mode": "linked",
  "linked_game_ids": ["987654321098", "555666777888"],
  "available_games": [
    {
      "game_id": "987654321098",
      "game_name": "Space Warriors",
      "developer_name": "Stellar Studios",
      "publisher_name": "Galaxy Entertainment",
      "genre": "Action",
      "platform": "Mobile",
      "game_status": "live",
      "game_icon": "https://cdn.invo.network/games/space_icon.png",
      "game_poster": "https://cdn.invo.network/games/space_poster.jpg",
      "game_url": "https://spacewarriors.com",
      "game_description": "Epic space combat game",
      "currency_name": "Energy Credits",
      "currency_symbol": "EC",
      "currency_symbol_url": "https://cdn.invo.network/currencies/ec_icon.png",
      "minimum_transfer": "5.00",
      "maximum_transfer": "1000.00"
    }
  ],
  "total_destinations": 1
}

No Destinations Available (200 OK)

{
  "status": "success",
  "source_game_id": "123456789012",
  "source_game_name": "Adventure Quest",
  "universal_transfers": "no",
  "transfer_mode": "linked",
  "linked_game_ids": [],
  "available_games": [],
  "total_destinations": 0,
  "message": "No linked games configured for transfers"
}

Error Response - Outgoing Transfers Disabled (403 Forbidden)

{
  "status": "error",
  "message": "Source game does not allow outgoing transfers"
}

Implementation Examples

UnityUnity C# Available Destinations with Caching

// This function should be called once daily and cached
public IEnumerator GetAvailableDestinations(string sourceGameId, Action<string> onSuccess, Action<string> onError)
{
    string url = "https://invo.network/api/transfers/available-destinations";
    
    var requestData = new {
        source_game_id = sourceGameId
    };
    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)
        {
            // Cache this response for 24 hours
            CacheDestinationsResponse(request.downloadHandler.text);
            onSuccess?.Invoke(request.downloadHandler.text);
        }
        else
        {
            onError?.Invoke(request.error + ": " + request.downloadHandler.text);
        }
    }
}

private void CacheDestinationsResponse(string jsonResponse)
{
    PlayerPrefs.SetString("cached_destinations", jsonResponse);
    PlayerPrefs.SetString("destinations_cache_time", System.DateTime.Now.ToBinary().ToString());
    PlayerPrefs.Save();
    Debug.Log("Destinations cached for 24 hours");
}

public string GetCachedDestinations()
{
    if (!PlayerPrefs.HasKey("cached_destinations")) return null;
    
    // Check if cache is expired (24 hours)
    string cacheTimeStr = PlayerPrefs.GetString("destinations_cache_time");
    if (!string.IsNullOrEmpty(cacheTimeStr))
    {
        long cacheTicks = System.Convert.ToInt64(cacheTimeStr);
        System.DateTime cacheTime = System.DateTime.FromBinary(cacheTicks);
        if ((System.DateTime.Now - cacheTime).TotalHours > 24)
        {
            // Cache expired
            return null;
        }
    }
    
    return PlayerPrefs.GetString("cached_destinations");
}

UnrealUnreal C++ Available Destinations with Caching

// This function should be called once daily and cached
void AYourGameMode::GetAvailableDestinations(const FString& SourceGameId)
{
    TSharedPtr<FJsonObject> RequestObj = MakeShareable(new FJsonObject);
    RequestObj->SetStringField("source_game_id", SourceGameId);

    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/transfers/available-destinations");
    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, &AYourGameMode::OnDestinationsReceived);
    Request->ProcessRequest();
}

void AYourGameMode::OnDestinationsReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
    if (bWasSuccessful && Response.IsValid())
    {
        FString ResponseContent = Response->GetContentAsString();
        
        if (Response->GetResponseCode() == 200)
        {
            // Cache the response for 24 hours
            CacheDestinationsResponse(ResponseContent);
            
            // Process the destinations data
            ProcessDestinationsResponse(ResponseContent);
        }
        else
        {
            UE_LOG(LogTemp, Error, TEXT("Failed to get destinations: %s"), *ResponseContent);
        }
    }
    else
    {
        UE_LOG(LogTemp, Error, TEXT("HTTP request failed"));
    }
}

void AYourGameMode::CacheDestinationsResponse(const FString& JsonResponse)
{
    // Save to game instance or persistent storage
    if (UGameInstanceSubsystem* GameInstance = GetGameInstance()->GetSubsystem<UGameInstanceSubsystem>())
    {
        GameInstance->SetCachedDestinations(JsonResponse);
        GameInstance->SetDestinationsCacheTime(FDateTime::Now());
        UE_LOG(LogTemp, Log, TEXT("Destinations cached for 24 hours"));
    }
}

FString AYourGameMode::GetCachedDestinations()
{
    if (UGameInstanceSubsystem* GameInstance = GetGameInstance()->GetSubsystem<UGameInstanceSubsystem>())
    {
        FDateTime CacheTime = GameInstance->GetDestinationsCacheTime();
        FDateTime Now = FDateTime::Now();
        
        // Check if cache is expired (24 hours)
        if ((Now - CacheTime).GetTotalHours() > 24.0)
        {
            return FString(); // Cache expired
        }
        
        return GameInstance->GetCachedDestinations();
    }
    
    return FString();
}

GodotGodot GDScript Available Destinations with Caching

# This function should be called once daily and cached
func get_available_destinations(source_game_id: String):
    var url = "https://invo.network/api/transfers/available-destinations"
    var headers = [
        "X-Game-Secret-Key: your_game_secret_key_here",
        "Content-Type: application/json"
    ]
    var body = {
        "source_game_id": source_game_id
    }
    
    var http_request = HTTPRequest.new()
    add_child(http_request)
    http_request.request_completed.connect(_on_destinations_received)
    
    http_request.request(url, headers, HTTPClient.METHOD_POST, JSON.stringify(body))

func _on_destinations_received(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
    var json_string = body.get_string_from_utf8()
    
    if response_code == 200:
        # Cache the response for 24 hours
        cache_destinations_response(json_string)
        
        # Process the destinations data
        process_destinations_response(json_string)
        print("Destinations retrieved and cached successfully")
    else:
        print("Failed to get destinations: ", json_string)

func cache_destinations_response(json_response: String):
    # Save to user data directory
    var file = FileAccess.open("user://cached_destinations.json", FileAccess.WRITE)
    if file:
        var cache_data = {
            "destinations": json_response,
            "cache_time": Time.get_unix_time_from_system()
        }
        file.store_string(JSON.stringify(cache_data))
        file.close()
        print("Destinations cached for 24 hours")

func get_cached_destinations() -> String:
    var file = FileAccess.open("user://cached_destinations.json", FileAccess.READ)
    if not file:
        return ""
    
    var cache_data_str = file.get_as_text()
    file.close()
    
    var json = JSON.new()
    var parse_result = json.parse(cache_data_str)
    if parse_result != OK:
        return ""
    
    var cache_data = json.data
    var cache_time = cache_data.get("cache_time", 0)
    var current_time = Time.get_unix_time_from_system()
    
    # Check if cache is expired (24 hours = 86400 seconds)
    if (current_time - cache_time) > 86400:
        print("Destinations cache expired")
        return ""
    
    return cache_data.get("destinations", "")

func should_refresh_destinations() -> bool:
    var cached = get_cached_destinations()
    return cached.is_empty()

# Call this on game startup
func initialize_destinations():
    if should_refresh_destinations():
        print("Refreshing destinations from API")
        get_available_destinations("123456789012")  # Your game ID
    else:
        print("Using cached destinations")
        var cached_data = get_cached_destinations()
        process_destinations_response(cached_data)
        
func process_destinations_response(json_response: String):
    var json = JSON.new()
    var parse_result = json.parse(json_response)
    if parse_result != OK:
        print("Error parsing destinations response")
        return
    
    var data = json.data
    if data.get("status") == "success":
        var available_games = data.get("available_games", [])
        print("Available destinations: ", available_games.size())
        # Build your transfer UI here using available_games data
    else:
        print("API error: ", data.get("message", "Unknown error"))

Building Transfer UI from Response Data

Response Data Usage Guide

FieldUI UsageExample
game_namePrimary display text"Space Warriors"
game_iconGame thumbnail/logo64x64px icon next to name
currency_nameCurrency description"Energy Credits"
currency_symbolCurrency abbreviation"EC" next to amounts
minimum_transferInput validationMinimum value for amount field
maximum_transferInput validationMaximum value (null = no limit)
developer_nameSecondary infoSmall text under game name
genreFiltering/categorizationGroup games by type

Sample Transfer UI Layout

Game icon
Space Warriors
by Stellar Studios • Action
Energy Credits (EC)
Min: 5.00 • Max: 1,000.00
Game icon
Fantasy Kingdom
by Magic Studios • RPG
Gold Coins (GC)
Min: 1.00 • No max

Error Handling

HTTP 400 - Bad Request

  • • Missing source_game_id in request body
  • • Invalid game ID format (not numeric)
  • • Source game not found

HTTP 403 - Forbidden

  • • Invalid or missing X-Game-Secret-Key header
  • • Authenticated game doesn't match source_game_id
  • • Source game has outgoing transfers disabled

HTTP 429 - Too Many Requests

  • • IP address exceeded 100 requests per minute
  • • Implement exponential backoff for retries
  • • This shouldn't happen with proper daily caching

HTTP 500 - Server Error

  • • Database connectivity issues
  • • Internal system errors
  • • Retry with exponential backoff
  • • Fall back to cached data if available

Implementation Checklist

Before Going Live