Uploading Audience Data to Another Users's AskRally Account Via API: A Tactical Guide

Uploading Audience Data to Another Users's AskRally Account Via API: A Tactical Guide

A technical deep-dive showing how to automate audience uploads to AskRally using its API instead of manually editing JSON files. This guide walks developers and agencies through a reliable two-step process to create and update audiences programmatically across multiple AskRally accounts.

October 06, 2025
← Back to Articles
Summarize in
OR

The Problem

You've built beautiful persona data. Your team (or client) has their AskRally account. Now you need to connect the two without copy-pasting 900+ base_memories.

I spent three hours the other day trying to do this, hitting gotchas (and finding a bug we've since fixed) so you don't have to.

Whether you're a consultant loading personas for clients, an agency managing multiple AskRally accounts, or a developer building persona management tools, this guide shows you exactly how to upload audience data programmatically, with working code.

The Solution: AskRally's Two-Step API Process

AskRally’s various API endpoints can support multiple workflows, but here’s one that worked well for me recently. I was working around a limitation in our audience export, which at the time didn’t support base_memories (a gap that’s since been filled). You might find yourself in a similar situation, especially if you’re supporting multiple teams and need to push audiences into users’ accounts, so here’s how I approached it.

Step 1: Create Empty Audience

First, I create an empty audience to get an audience ID. 

```python
def create_audience(self, name, description):
endpoint = f"{self.base_url}/audiences"
payload = {
"description": description,
"name": name,
"personas": [] # Empty array required
}
response = requests.post(endpoint, headers=self.headers, json=payload)
return response.json() # Returns {"audience_id": "r123...", "message": "..."}
```


Then I'd get Cursor triggering this curl command.


curl -X 'POST' \
  'https://api.askrally.com/api/v1/audiences' \
  -H 'Authorization: Bearer rally_sk_your_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "description": "Test audience",
    "name": "Test Audience", 
    "personas": []
  }'


Note: Technically, you don't need to create an audience before you add personas. Our create audience end point (/api/v1/audiences) will support adding personas. It just did'nt support adding base_memories at the time (solved now), so I had to first create the audience and then edit it with our update end poing (/api/v1/audiences/{audience_id}

Step 2: Update Audience with Personas

I'd use the audience ID to update the existing audience to contain the personas:

```python
def update_audience_with_personas(self, audience_id, personas, name, description):
endpoint = f"{self.base_url}/audiences/{audience_id}"

# Transform personas for AskRally API format
api_personas = []
for i, persona in enumerate(personas):
api_persona = persona.copy()

# Add required id field if not present
if 'id' not in api_persona:
api_persona['id'] = f"persona_{i+1}"

# Convert manual_memories to base_memories for API
if 'manual_memories' in api_persona:
api_persona['base_memories'] = api_persona.pop('manual_memories')
api_personas.append(api_persona)

# Full audience payload for PUT request
payload = {
"description": description,
"name": name,
"personas": api_personas
}

response = requests.put(endpoint, headers=self.headers, json=payload)
return response.json()
```


Then I'd get Cursor triggering this curl command.


curl -X 'PUT' \
  'https://api.askrally.com/api/v1/audiences/r123abc456' \
  -H 'Authorization: Bearer rally_sk_your_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "description": "Test audience",
    "name": "Test Audience",
    "personas": [{"name": "Test", "id": "persona_1", "base_memories": []}]
  }'


When uploading to a client's account, you'll need them to:

1. Log into their AskRally account

2. Go to Account Settings → API Keys

3. Generate a new API key (starts with `rally_sk_`)

4. Share it with you securely

5. Then use it in your script:

```python
uploader = AskRallyUploader(api_key="REPLACE WITH THEIR API KEY HERE")
result = uploader.upload_audience_file("audience_data.json")
```


Real Upload Example

Here's what happens when you upload a converted file:


šŸŽÆ AskRally Audience Uploader

==================================================

šŸ“ Loading audience file: marketing_professionals.json

šŸ“Š File contains:

- Name: Marketing Professionals

- Personas: 10

- Total memories: 925

==================================================

STEP 1: Creating Empty Audience

==================================================

šŸš€ Creating audience: Marketing Professionals

šŸ“” POST https://api.askrally.com/api/v1/audiences

āœ… Audience created successfully!

- ID: rb25ab7b0a82c4f

==================================================

STEP 2: Uploading Personas

==================================================

šŸ”„ Updating audience rb25ab7b0a82c4f with 10 personas

šŸ“” PUT https://api.askrally.com/api/v1/audiences/rb25ab7b0a82c4f

āœ… Audience updated successfully!

- Personas added: 10

šŸŽ‰ UPLOAD COMPLETED SUCCESSFULLY!

āœ… Audience ID: rb25ab7b0a82c4f

āœ… Personas Uploaded: 10

āœ… Total Memories: 925


Need help with your audience data? 
DM me on for a quote on our pro-services.


Complete Working Code 

#!/usr/bin/env python3

"""

Complete working code for uploading audience data to AskRally via API.

This is the full implementation referenced in the guide article.

"""

import json

import os

import requests

from typing import Dict, List, Any, Optional

class AskRallyUploader:

"""Complete AskRally API uploader with two-step process."""



def __init__(self, api_key: str):

"""Initialize with API key."""

self.api_key = api_key

self.base_url = "https://api.askrally.com/api/v1"

self.headers = {

'Authorization': f'Bearer {api_key}',

'Content-Type': 'application/json',

'Accept': 'application/json'

}



def create_audience(self, name: str, description: str = "") -> Dict[str, Any]:

"""

Step 1: Create empty audience to get audience ID.



Args:

name: Name of the audience

description: Description of the audience



Returns:

API response containing audience_id

"""

endpoint = f"{self.base_url}/audiences"



payload = {

"description": description,

"name": name,

"personas": [] # Empty array required

}



print(f"šŸš€ Creating audience: {name}")

print(f"šŸ“” POST {endpoint}")



response = requests.post(endpoint, headers=self.headers, json=payload)

response.raise_for_status()



audience_data = response.json()

print(f"āœ… Audience created successfully!")

print(f" - ID: {audience_data.get('audience_id', 'N/A')}")



return audience_data



def update_audience_with_personas(self, audience_id: str, personas: List[Dict[str, Any]],

name: str, description: str) -> Dict[str, Any]:

"""

Step 2: Update audience with personas.



Args:

audience_id: ID from create_audience step

personas: List of persona objects

name: Audience name

description: Audience description



Returns:

API response from update

"""

endpoint = f"{self.base_url}/audiences/{audience_id}"



print(f"šŸ”„ Updating audience {audience_id} with {len(personas)} personas")

print(f"šŸ“” PUT {endpoint}")



# Transform personas for AskRally API format

api_personas = []

for i, persona in enumerate(personas):

api_persona = persona.copy()



# Add required id field if not present

if 'id' not in api_persona:

api_persona['id'] = f"persona_{i+1}"



# Convert manual_memories to base_memories for API

if 'manual_memories' in api_persona:

api_persona['base_memories'] = api_persona.pop('manual_memories')



api_personas.append(api_persona)



# Full audience payload for PUT request

payload = {

"description": description,

"name": name,

"personas": api_personas

}



response = requests.put(endpoint, headers=self.headers, json=payload)

response.raise_for_status()



update_data = response.json()

print(f"āœ… Audience updated successfully!")

print(f" - Personas added: {len(personas)}")



return update_data



def upload_audience_file(self, file_path: str) -> Dict[str, Any]:

"""

Complete workflow: Load file and execute two-step upload.



Args:

file_path: Path to audience JSON file



Returns:

Complete results from both API calls

"""

print(f"šŸ“ Loading audience file: {file_path}")



# Load file

with open(file_path, 'r', encoding='utf-8') as f:

audience_data = json.load(f)



name = audience_data['name']

description = audience_data.get('description', '')

personas = audience_data['personas']



print(f"šŸ“Š File contains:")

print(f" - Name: {name}")

print(f" - Personas: {len(personas)}")

print(f" - Total memories: {sum(len(p.get('manual_memories', [])) for p in personas)}")



print("\n" + "="*50)

print("STEP 1: Creating Empty Audience")

print("="*50)



# Step 1: Create empty audience

audience_result = self.create_audience(name, description)

audience_id = audience_result.get('audience_id')



if not audience_id:

raise ValueError("Failed to get audience_id from creation response")



print("\n" + "="*50)

print("STEP 2: Uploading Personas")

print("="*50)



# Step 2: Update with personas

update_result = self.update_audience_with_personas(audience_id, personas, name, description)



print("\n" + "="*50)

print("šŸŽ‰ UPLOAD COMPLETED SUCCESSFULLY!")

print("="*50)

print(f"āœ… Audience ID: {audience_id}")

print(f"āœ… Audience Name: {name}")

print(f"āœ… Personas Uploaded: {len(personas)}")

print(f"āœ… Total Memories: {sum(len(p.get('manual_memories', [])) for p in personas)}")



return {

'audience_creation': audience_result,

'personas_upload': update_result,

'audience_id': audience_id,

'audience_name': name,

'personas_count': len(personas),

'total_memories': sum(len(p.get('manual_memories', [])) for p in personas)

}

def main():

"""Example usage of the uploader."""

import sys



if len(sys.argv) != 3:

print("Usage: python upload_to_askrally.py <api_key> <audience_file>")

print("\nExample:")

print(" python upload_to_askrally.py rally_sk_abc123... audience.json")

sys.exit(1)



api_key = sys.argv[1]

file_path = sys.argv[2]



try:

uploader = AskRallyUploader(api_key)

result = uploader.upload_audience_file(file_path)



print(f"\nšŸ“ Upload completed!")

print(f" Audience ID: {result['audience_id']}")

print(f" You can now view this audience in your AskRally dashboard")



except Exception as e:

print(f"\nāŒ Upload failed: {e}")

sys.exit(1)

if __name__ == "__main__":

main()

# Alternative usage examples:

# Example 1: Direct instantiation

"""

uploader = AskRallyUploader("rally_sk_your_api_key_here")

result = uploader.upload_audience_file("audience.json")

print(f"Uploaded to audience: {result['audience_id']}")

"""

# Example 2: Environment variable

"""

api_key = os.getenv('ASKRALLY_API_KEY')

uploader = AskRallyUploader(api_key)

result = uploader.upload_audience_file("audience.json")

"""

# Example 3: Just the two API calls

"""

uploader = AskRallyUploader("rally_sk_your_api_key_here")

# Step 1: Create empty audience

audience_result = uploader.create_audience("Test Audience", "Test description")

audience_id = audience_result['audience_id']

# Step 2: Add personas

personas = [{"name": "Test Person", "age": 30, "manual_memories": []}]

update_result = uploader.update_audience_with_personas(

audience_id, personas, "Test Audience", "Test description"

)

"""

 

Stay Updated

Subscribe to our newsletter to get notified about new articles and updates.

Rhys Fisher
Rhys Fisher

Rhys Fisher is the COO & Co-Founder of Rally. He previously co-founded a boutique analytics agency called Unvanity, crossed the Pyrenees coast-to-coast via paraglider, and now watches virtual crowds respond to memes. Follow him on Twitter @virtual_rf

← Back to Articles