Home Docs Python

Python Quick Start

Integrate Indieop API into your Python games using Pygame, Panda3D, or other frameworks.

Prerequisites

Before you begin, make sure you have:

  1. Created an Indieop account
  2. Created a game and obtained your API key from the game settings
  3. Python 3.7 or higher installed
  4. The requests library: pip install requests

Overview

This guide shows you how to send player feedback and form submissions from your Python game to the Indieop API. Works with:

  • Pygame: Popular 2D game development library
  • Panda3D: 3D game engine with Python
  • Arcade: Modern Python framework for 2D games
  • Ren'Py: Visual novel engine

Basic Implementation

Step 1: Install Dependencies

Install the requests library if you haven't already:

pip install requests

Step 2: Create the API Client

Create a new file called indieop_client.py:

"""
IndieOp API Client for Python Games
"""
import requests
import json
from typing import Dict, List, Optional, Union, Any


class IndieOpClient:
    """Client for submitting player feedback to IndieOp API"""
    
    BASE_URL = "https://indieop.com/api/sdk"
    
    def __init__(self, api_key: str):
        """
        Initialize the IndieOp client
        
        Args:
            api_key: Your game's API key from the IndieOp dashboard
        """
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "X-API-Key": api_key,
            "Content-Type": "application/json"
        })
    
    def submit_form(self, submission_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Submit a form to the IndieOp API
        
        Args:
            submission_data: Dictionary containing form data
            
        Returns:
            Dictionary with 'success' and 'data' or 'error' keys
            
        Raises:
            requests.RequestException: If the request fails
        """
        url = f"{self.BASE_URL}/submit"
        
        try:
            response = self.session.post(url, json=submission_data, timeout=10)
            response_data = response.json()
            
            if response.status_code in (200, 201):
                print(f"✓ Submission successful: {response_data['message']}")
                return {
                    "success": True,
                    "data": response_data.get("data", {})
                }
            else:
                print(f"✗ Submission failed: {response_data.get('message', 'Unknown error')}")
                return {
                    "success": False,
                    "error": response_data.get("message", "Unknown error"),
                    "errors": response_data.get("errors", {})
                }
                
        except requests.exceptions.RequestException as e:
            print(f"✗ Network error: {str(e)}")
            return {
                "success": False,
                "error": f"Network error: {str(e)}"
            }
    
    @staticmethod
    def create_field(key: str, label: str, field_type: str, 
                    value: Union[str, int, bool], 
                    options: Optional[List[str]] = None) -> Dict[str, Any]:
        """
        Helper method to create a properly formatted field
        
        Args:
            key: Unique identifier for the field
            label: Display label for the field
            field_type: Type of field (text, rating, checkbox, dropdown)
            value: The field value
            options: List of options (required for dropdown fields)
            
        Returns:
            Dictionary representing the field
        """
        field = {
            "key": key,
            "label": label,
            "type": field_type,
            "value": value
        }
        
        if options is not None:
            field["options"] = options
            
        return field
    
    def create_submission(self, form_name: str, tag: str, 
                         fields: List[Dict[str, Any]],
                         form_type: str = "general",
                         player_identifier: Optional[str] = None,
                         game_version: Optional[str] = None,
                         repository_version: Optional[str] = None) -> Dict[str, Any]:
        """
        Helper method to create a submission dictionary
        
        Args:
            form_name: Name of the form
            tag: Unique tag for the form
            fields: List of field dictionaries
            form_type: Type of form (general, feedback, bug_report)
            player_identifier: Optional player ID
            game_version: Optional game version
            repository_version: Optional repository version
            
        Returns:
            Dictionary ready to be submitted
        """
        submission = {
            "form_name": form_name,
            "tag": tag,
            "form_type": form_type,
            "fields": fields
        }
        
        if player_identifier:
            submission["player_identifier"] = player_identifier
        if game_version:
            submission["game_version"] = game_version
        if repository_version:
            submission["repository_version"] = repository_version
            
        return submission

Step 3: Use the Client

Here's a simple example of using the client:

from indieop_client import IndieOpClient

# Initialize the client
client = IndieOpClient("your_game_api_key_here")

# Create fields
fields = [
    client.create_field("rating", "Overall Rating", "rating", 5),
    client.create_field("comments", "Your Feedback", "text", "Amazing game!"),
    client.create_field("difficulty", "Difficulty Level", "dropdown", "Medium",
                       options=["Easy", "Medium", "Hard"]),
    client.create_field("enjoyed", "Did you enjoy?", "checkbox", True)
]

# Create submission
submission = client.create_submission(
    form_name="Player Feedback",
    tag="feedback",
    fields=fields,
    form_type="feedback",
    game_version="1.0.0"
)

# Submit
result = client.submit_form(submission)

if result["success"]:
    print("Thank you for your feedback!")
else:
    print(f"Error: {result['error']}")

Pygame Integration Example

Here's how to integrate feedback collection into a Pygame game:

import pygame
import sys
from indieop_client import IndieOpClient

# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()

# Initialize IndieOp client
indieop = IndieOpClient("your_game_api_key_here")

class FeedbackForm:
    def __init__(self):
        self.active = False
        self.rating = 5
        self.comments = ""
        self.submitted = False
    
    def show(self):
        """Display feedback form"""
        self.active = True
    
    def handle_event(self, event):
        """Handle input events for the form"""
        if not self.active:
            return
        
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN:
                self.submit()
            elif event.key == pygame.K_ESCAPE:
                self.active = False
            elif event.key == pygame.K_BACKSPACE:
                self.comments = self.comments[:-1]
            elif event.unicode.isprintable():
                self.comments += event.unicode
    
    def submit(self):
        """Submit the feedback"""
        fields = [
            indieop.create_field("rating", "Overall Rating", "rating", self.rating),
            indieop.create_field("comments", "Comments", "text", self.comments)
        ]
        
        submission = indieop.create_submission(
            form_name="Player Feedback",
            tag="feedback",
            fields=fields,
            form_type="feedback",
            game_version="1.0.0"
        )
        
        result = indieop.submit_form(submission)
        
        if result["success"]:
            self.submitted = True
            print("Feedback submitted successfully!")
        else:
            print(f"Failed to submit: {result['error']}")
        
        self.active = False
    
    def draw(self, surface):
        """Draw the feedback form"""
        if not self.active:
            return
        
        # Draw semi-transparent overlay
        overlay = pygame.Surface((800, 600))
        overlay.set_alpha(200)
        overlay.fill((0, 0, 0))
        surface.blit(overlay, (0, 0))
        
        # Draw form background
        form_rect = pygame.Rect(150, 150, 500, 300)
        pygame.draw.rect(surface, (255, 255, 255), form_rect)
        pygame.draw.rect(surface, (0, 0, 0), form_rect, 2)
        
        # Draw text
        font = pygame.font.Font(None, 32)
        title = font.render("Rate Your Experience", True, (0, 0, 0))
        surface.blit(title, (200, 180))
        
        # Draw rating
        rating_text = font.render(f"Rating: {self.rating}/5", True, (0, 0, 0))
        surface.blit(rating_text, (200, 230))
        
        # Draw comments
        comments_font = pygame.font.Font(None, 24)
        comments_text = comments_font.render(f"Comments: {self.comments}", True, (0, 0, 0))
        surface.blit(comments_text, (200, 280))
        
        # Draw instructions
        instruction_font = pygame.font.Font(None, 20)
        instructions = [
            "Type your comments",
            "Press ENTER to submit",
            "Press ESC to cancel"
        ]
        y = 350
        for instruction in instructions:
            text = instruction_font.render(instruction, True, (100, 100, 100))
            surface.blit(text, (200, y))
            y += 25

# Game loop
feedback_form = FeedbackForm()
running = True

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        # Handle feedback form events
        feedback_form.handle_event(event)
        
        # Show feedback form on F key
        if event.type == pygame.KEYDOWN and event.key == pygame.K_f:
            feedback_form.show()
    
    # Clear screen
    screen.fill((50, 50, 50))
    
    # Draw game content here
    font = pygame.font.Font(None, 36)
    text = font.render("Press F for Feedback", True, (255, 255, 255))
    screen.blit(text, (250, 280))
    
    # Draw feedback form on top
    feedback_form.draw(screen)
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

Async Implementation (Optional)

For non-blocking submissions, use an async version:

"""
Async IndieOp Client using aiohttp
Install: pip install aiohttp
"""
import aiohttp
import asyncio
from typing import Dict, Any


class AsyncIndieOpClient:
    """Async client for non-blocking submissions"""
    
    BASE_URL = "https://indieop.com/api/sdk"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.headers = {
            "X-API-Key": api_key,
            "Content-Type": "application/json"
        }
    
    async def submit_form(self, submission_data: Dict[str, Any]) -> Dict[str, Any]:
        """Submit form asynchronously"""
        url = f"{self.BASE_URL}/submit"
        
        try:
            async with aiohttp.ClientSession() as session:
                async with session.post(url, json=submission_data, 
                                       headers=self.headers, 
                                       timeout=10) as response:
                    response_data = await response.json()
                    
                    if response.status in (200, 201):
                        print(f"✓ Submission successful")
                        return {"success": True, "data": response_data.get("data", {})}
                    else:
                        print(f"✗ Submission failed: {response_data.get('message')}")
                        return {"success": False, "error": response_data.get("message")}
                        
        except aiohttp.ClientError as e:
            print(f"✗ Network error: {str(e)}")
            return {"success": False, "error": str(e)}


# Usage example
async def main():
    client = AsyncIndieOpClient("your_game_api_key_here")
    
    submission = {
        "form_name": "Player Feedback",
        "tag": "feedback",
        "form_type": "feedback",
        "game_version": "1.0.0",
        "fields": [
            {"key": "rating", "label": "Rating", "type": "rating", "value": 5},
            {"key": "comments", "label": "Comments", "type": "text", "value": "Great!"}
        ]
    }
    
    result = await client.submit_form(submission)
    print(result)

# Run async code
if __name__ == "__main__":
    asyncio.run(main())

Background Submission (Threading)

To avoid blocking your game loop, submit in a background thread:

import threading
from indieop_client import IndieOpClient

class BackgroundSubmitter:
    """Submit forms in background threads to avoid blocking"""
    
    def __init__(self, api_key: str):
        self.client = IndieOpClient(api_key)
    
    def submit_async(self, submission_data, callback=None):
        """Submit in a background thread"""
        def submit():
            result = self.client.submit_form(submission_data)
            if callback:
                callback(result)
        
        thread = threading.Thread(target=submit, daemon=True)
        thread.start()


# Usage in your game
submitter = BackgroundSubmitter("your_game_api_key_here")

def on_submit_complete(result):
    if result["success"]:
        print("Feedback submitted successfully!")
    else:
        print(f"Failed: {result['error']}")

# Submit without blocking
submission = {
    "form_name": "Player Feedback",
    "tag": "feedback",
    "form_type": "feedback",
    "fields": [
        {"key": "rating", "label": "Rating", "type": "rating", "value": 5}
    ]
}

submitter.submit_async(submission, on_submit_complete)
print("Game continues running while submission happens in background...")

Field Types Reference

The Indieop API supports four field types:

Field Type Python Type Example
text str "Great game!"
rating int (1-5) 5
checkbox bool True
dropdown str (from List) "Medium"

Best Practices

  • Use environment variables: Store your API key in .env files, never in code
  • Background threads: Use threading or async to avoid blocking your game loop
  • Error handling: Always handle network errors gracefully
  • Queue failed submissions: Store locally if submission fails and retry later
  • Type hints: Use Python type hints for better code quality
  • Rate limiting: Don't exceed 100 requests per minute

Next Steps