# Apple Search Ads Python Client
A Python client library for Apple Search Ads API v5, providing a simple and intuitive interface for managing and reporting on Apple Search Ads campaigns.
## Features
- 🔐 OAuth2 authentication with JWT
- 📊 Campaign performance reporting
- 🏢 Multi-organization support
- 💰 Spend tracking by app
- ⚡ Built-in rate limiting
- 🐼 Pandas DataFrames for easy data manipulation
- 🔄 Automatic token refresh
- 🎯 Type hints for better IDE support
- ✅ 100% test coverage
## Installation
```bash
pip install apple-search-ads-client
```
## Quick Start
```python
from apple_search_ads import AppleSearchAdsClient
# Initialize the client
client = AppleSearchAdsClient(
client_id="your_client_id",
team_id="your_team_id",
key_id="your_key_id",
private_key_path="/path/to/private_key.p8"
)
# Get all campaigns
campaigns = client.get_campaigns()
# Get daily spend for the last 30 days
spend_df = client.get_daily_spend(days=30)
print(spend_df)
```
## Authentication
### Prerequisites
1. An Apple Search Ads account with API access
2. API credentials from the Apple Search Ads UI:
- Client ID
- Team ID
- Key ID
- Private key file (.p8)
### Setting up credentials
You can provide credentials in three ways:
#### 1. Direct parameters (recommended)
```python
client = AppleSearchAdsClient(
client_id="your_client_id",
team_id="your_team_id",
key_id="your_key_id",
private_key_path="/path/to/private_key.p8"
)
```
#### 2. Environment variables
```bash
export APPLE_SEARCH_ADS_CLIENT_ID="your_client_id"
export APPLE_SEARCH_ADS_TEAM_ID="your_team_id"
export APPLE_SEARCH_ADS_KEY_ID="your_key_id"
export APPLE_SEARCH_ADS_PRIVATE_KEY_PATH="/path/to/private_key.p8"
```
```python
client = AppleSearchAdsClient() # Will use environment variables
```
#### 3. Private key content
```python
# Useful for environments where file access is limited
with open("private_key.p8", "r") as f:
private_key_content = f.read()
client = AppleSearchAdsClient(
client_id="your_client_id",
team_id="your_team_id",
key_id="your_key_id",
private_key_content=private_key_content
)
```
## Usage Examples
### Get all organizations
```python
# List all organizations you have access to
orgs = client.get_all_organizations()
for org in orgs:
print(f"{org['orgName']} - {org['orgId']}")
```
### Get campaign performance report
```python
from datetime import datetime, timedelta
# Get campaign performance for the last 7 days
end_date = datetime.now()
start_date = end_date - timedelta(days=7)
report_df = client.get_campaign_report(
start_date=start_date,
end_date=end_date,
granularity="DAILY" # Options: DAILY, WEEKLY, MONTHLY
)
# Display key metrics
print(report_df[['date', 'campaign_name', 'spend', 'installs', 'taps']])
```
### Track spend by app
```python
# Get daily spend grouped by app
app_spend_df = client.get_daily_spend_by_app(
start_date="2024-01-01",
end_date="2024-01-31",
fetch_all_orgs=True # Fetch from all organizations
)
# Group by app and sum
app_totals = app_spend_df.groupby('app_id').agg({
'spend': 'sum',
'installs': 'sum',
'impressions': 'sum'
}).round(2)
print(app_totals)
```
### Get campaigns from all organizations
```python
# Fetch campaigns across all organizations
all_campaigns = client.get_all_campaigns()
# Filter active campaigns
active_campaigns = [c for c in all_campaigns if c['status'] == 'ENABLED']
print(f"Found {len(active_campaigns)} active campaigns across all orgs")
```
### Working with specific organization
```python
# Get campaigns for a specific org
org_id = "123456"
campaigns = client.get_campaigns(org_id=org_id)
# The client will use this org for subsequent requests
```
### Working with ad groups
```python
# Get ad groups for a campaign
campaign_id = "1234567890"
adgroups = client.get_adgroups(campaign_id)
for adgroup in adgroups:
print(f"Ad Group: {adgroup['name']} (Status: {adgroup['status']})")
```
## API Reference
### Client initialization
```python
AppleSearchAdsClient(
client_id: Optional[str] = None,
team_id: Optional[str] = None,
key_id: Optional[str] = None,
private_key_path: Optional[str] = None,
private_key_content: Optional[str] = None,
org_id: Optional[str] = None
)
```
### Methods
#### Organizations
- `get_all_organizations()` - Get all organizations
- `get_campaigns(org_id: Optional[str] = None)` - Get campaigns for an organization
- `get_all_campaigns()` - Get campaigns from all organizations
#### Reporting
- `get_campaign_report(start_date, end_date, granularity="DAILY")` - Get campaign performance report
- `get_daily_spend(days=30, fetch_all_orgs=True)` - Get daily spend for the last N days
- `get_daily_spend_with_dates(start_date, end_date, fetch_all_orgs=True)` - Get daily spend for date range
- `get_daily_spend_by_app(start_date, end_date, fetch_all_orgs=True)` - Get spend grouped by app
#### Campaign Management
- `get_campaigns_with_details(fetch_all_orgs=True)` - Get campaigns with app details
- `get_adgroups(campaign_id)` - Get ad groups for a specific campaign
## DataFrame Output
All reporting methods return pandas DataFrames for easy data manipulation:
```python
# Example: Calculate weekly totals
daily_spend = client.get_daily_spend(days=30)
daily_spend['week'] = pd.to_datetime(daily_spend['date']).dt.isocalendar().week
weekly_totals = daily_spend.groupby('week')['spend'].sum()
```
## Rate Limiting
The client includes built-in rate limiting to respect Apple's API limits (10 requests per second). You don't need to implement any additional rate limiting.
## Error Handling
```python
from apple_search_ads.exceptions import (
AuthenticationError,
RateLimitError,
OrganizationNotFoundError
)
try:
campaigns = client.get_campaigns()
except AuthenticationError as e:
print(f"Authentication failed: {e}")
except RateLimitError as e:
print(f"Rate limit exceeded: {e}")
except Exception as e:
print(f"An error occurred: {e}")
```
## Best Practices
1. **Reuse client instances**: Create one client and reuse it for multiple requests
2. **Use date ranges wisely**: Large date ranges may result in slower responses
3. **Cache organization IDs**: If working with specific orgs frequently, cache their IDs
4. **Monitor rate limits**: Although built-in rate limiting is included, be mindful of your usage
5. **Use DataFrame operations**: Leverage pandas for data aggregation and analysis
## Requirements
- Python 3.8 or higher
- See `requirements.txt` for package dependencies
## Testing
This project maintains **100% test coverage**. The test suite includes:
- Unit tests with mocked API responses
- Exception handling tests
- Edge case coverage
- Legacy API format compatibility tests
- Comprehensive integration tests
### Running Tests
```bash
# Run all tests with coverage report
pytest tests -v --cov=apple_search_ads --cov-report=term-missing
# Run tests in parallel for faster execution
pytest tests -n auto
# Generate HTML coverage report
pytest tests --cov=apple_search_ads --cov-report=html
# Run integration tests (requires credentials)
pytest tests/test_integration.py -v
```
For detailed testing documentation, see [TESTING.md](TESTING.md).
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Support
- 🐛 Issues: [GitHub Issues](https://github.com/bickster/apple-search-ads-python/issues)
- 📖 Documentation: [Read the Docs](https://apple-search-ads-python.readthedocs.io/)
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for a list of changes.
## Acknowledgments
- Apple for providing the Search Ads API
- The Python community for excellent libraries used in this project
Raw data
{
"_id": null,
"home_page": "https://github.com/bickster/apple-search-ads-python",
"name": "apple-search-ads-client",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "apple, search, ads, api, marketing, advertising, ios, app, store",
"author": "Bickster LLC",
"author_email": "Bickster LLC <support@bickster.com>",
"download_url": "https://files.pythonhosted.org/packages/23/26/528bb3661b93698bcb077314d13b9bc337b22d0ab20542b04d2fe3625c6d/apple_search_ads_client-1.0.9.tar.gz",
"platform": null,
"description": "# Apple Search Ads Python Client\n\nA Python client library for Apple Search Ads API v5, providing a simple and intuitive interface for managing and reporting on Apple Search Ads campaigns.\n\n## Features\n\n- \ud83d\udd10 OAuth2 authentication with JWT\n- \ud83d\udcca Campaign performance reporting\n- \ud83c\udfe2 Multi-organization support\n- \ud83d\udcb0 Spend tracking by app\n- \u26a1 Built-in rate limiting\n- \ud83d\udc3c Pandas DataFrames for easy data manipulation\n- \ud83d\udd04 Automatic token refresh\n- \ud83c\udfaf Type hints for better IDE support\n- \u2705 100% test coverage\n\n## Installation\n\n```bash\npip install apple-search-ads-client\n```\n\n## Quick Start\n\n```python\nfrom apple_search_ads import AppleSearchAdsClient\n\n# Initialize the client\nclient = AppleSearchAdsClient(\n client_id=\"your_client_id\",\n team_id=\"your_team_id\",\n key_id=\"your_key_id\",\n private_key_path=\"/path/to/private_key.p8\"\n)\n\n# Get all campaigns\ncampaigns = client.get_campaigns()\n\n# Get daily spend for the last 30 days\nspend_df = client.get_daily_spend(days=30)\nprint(spend_df)\n```\n\n## Authentication\n\n### Prerequisites\n\n1. An Apple Search Ads account with API access\n2. API credentials from the Apple Search Ads UI:\n - Client ID\n - Team ID\n - Key ID\n - Private key file (.p8)\n\n### Setting up credentials\n\nYou can provide credentials in three ways:\n\n#### 1. Direct parameters (recommended)\n\n```python\nclient = AppleSearchAdsClient(\n client_id=\"your_client_id\",\n team_id=\"your_team_id\",\n key_id=\"your_key_id\",\n private_key_path=\"/path/to/private_key.p8\"\n)\n```\n\n#### 2. Environment variables\n\n```bash\nexport APPLE_SEARCH_ADS_CLIENT_ID=\"your_client_id\"\nexport APPLE_SEARCH_ADS_TEAM_ID=\"your_team_id\"\nexport APPLE_SEARCH_ADS_KEY_ID=\"your_key_id\"\nexport APPLE_SEARCH_ADS_PRIVATE_KEY_PATH=\"/path/to/private_key.p8\"\n```\n\n```python\nclient = AppleSearchAdsClient() # Will use environment variables\n```\n\n#### 3. Private key content\n\n```python\n# Useful for environments where file access is limited\nwith open(\"private_key.p8\", \"r\") as f:\n private_key_content = f.read()\n\nclient = AppleSearchAdsClient(\n client_id=\"your_client_id\",\n team_id=\"your_team_id\",\n key_id=\"your_key_id\",\n private_key_content=private_key_content\n)\n```\n\n## Usage Examples\n\n### Get all organizations\n\n```python\n# List all organizations you have access to\norgs = client.get_all_organizations()\nfor org in orgs:\n print(f\"{org['orgName']} - {org['orgId']}\")\n```\n\n### Get campaign performance report\n\n```python\nfrom datetime import datetime, timedelta\n\n# Get campaign performance for the last 7 days\nend_date = datetime.now()\nstart_date = end_date - timedelta(days=7)\n\nreport_df = client.get_campaign_report(\n start_date=start_date,\n end_date=end_date,\n granularity=\"DAILY\" # Options: DAILY, WEEKLY, MONTHLY\n)\n\n# Display key metrics\nprint(report_df[['date', 'campaign_name', 'spend', 'installs', 'taps']])\n```\n\n### Track spend by app\n\n```python\n# Get daily spend grouped by app\napp_spend_df = client.get_daily_spend_by_app(\n start_date=\"2024-01-01\",\n end_date=\"2024-01-31\",\n fetch_all_orgs=True # Fetch from all organizations\n)\n\n# Group by app and sum\napp_totals = app_spend_df.groupby('app_id').agg({\n 'spend': 'sum',\n 'installs': 'sum',\n 'impressions': 'sum'\n}).round(2)\n\nprint(app_totals)\n```\n\n### Get campaigns from all organizations\n\n```python\n# Fetch campaigns across all organizations\nall_campaigns = client.get_all_campaigns()\n\n# Filter active campaigns\nactive_campaigns = [c for c in all_campaigns if c['status'] == 'ENABLED']\n\nprint(f\"Found {len(active_campaigns)} active campaigns across all orgs\")\n```\n\n### Working with specific organization\n\n```python\n# Get campaigns for a specific org\norg_id = \"123456\"\ncampaigns = client.get_campaigns(org_id=org_id)\n\n# The client will use this org for subsequent requests\n```\n\n### Working with ad groups\n\n```python\n# Get ad groups for a campaign\ncampaign_id = \"1234567890\"\nadgroups = client.get_adgroups(campaign_id)\n\nfor adgroup in adgroups:\n print(f\"Ad Group: {adgroup['name']} (Status: {adgroup['status']})\")\n```\n\n## API Reference\n\n### Client initialization\n\n```python\nAppleSearchAdsClient(\n client_id: Optional[str] = None,\n team_id: Optional[str] = None,\n key_id: Optional[str] = None,\n private_key_path: Optional[str] = None,\n private_key_content: Optional[str] = None,\n org_id: Optional[str] = None\n)\n```\n\n### Methods\n\n#### Organizations\n\n- `get_all_organizations()` - Get all organizations\n- `get_campaigns(org_id: Optional[str] = None)` - Get campaigns for an organization\n- `get_all_campaigns()` - Get campaigns from all organizations\n\n#### Reporting\n\n- `get_campaign_report(start_date, end_date, granularity=\"DAILY\")` - Get campaign performance report\n- `get_daily_spend(days=30, fetch_all_orgs=True)` - Get daily spend for the last N days\n- `get_daily_spend_with_dates(start_date, end_date, fetch_all_orgs=True)` - Get daily spend for date range\n- `get_daily_spend_by_app(start_date, end_date, fetch_all_orgs=True)` - Get spend grouped by app\n\n#### Campaign Management\n\n- `get_campaigns_with_details(fetch_all_orgs=True)` - Get campaigns with app details\n- `get_adgroups(campaign_id)` - Get ad groups for a specific campaign\n\n## DataFrame Output\n\nAll reporting methods return pandas DataFrames for easy data manipulation:\n\n```python\n# Example: Calculate weekly totals\ndaily_spend = client.get_daily_spend(days=30)\ndaily_spend['week'] = pd.to_datetime(daily_spend['date']).dt.isocalendar().week\nweekly_totals = daily_spend.groupby('week')['spend'].sum()\n```\n\n## Rate Limiting\n\nThe client includes built-in rate limiting to respect Apple's API limits (10 requests per second). You don't need to implement any additional rate limiting.\n\n## Error Handling\n\n```python\nfrom apple_search_ads.exceptions import (\n AuthenticationError,\n RateLimitError,\n OrganizationNotFoundError\n)\n\ntry:\n campaigns = client.get_campaigns()\nexcept AuthenticationError as e:\n print(f\"Authentication failed: {e}\")\nexcept RateLimitError as e:\n print(f\"Rate limit exceeded: {e}\")\nexcept Exception as e:\n print(f\"An error occurred: {e}\")\n```\n\n## Best Practices\n\n1. **Reuse client instances**: Create one client and reuse it for multiple requests\n2. **Use date ranges wisely**: Large date ranges may result in slower responses\n3. **Cache organization IDs**: If working with specific orgs frequently, cache their IDs\n4. **Monitor rate limits**: Although built-in rate limiting is included, be mindful of your usage\n5. **Use DataFrame operations**: Leverage pandas for data aggregation and analysis\n\n## Requirements\n\n- Python 3.8 or higher\n- See `requirements.txt` for package dependencies\n\n## Testing\n\nThis project maintains **100% test coverage**. The test suite includes:\n\n- Unit tests with mocked API responses\n- Exception handling tests\n- Edge case coverage\n- Legacy API format compatibility tests\n- Comprehensive integration tests\n\n### Running Tests\n\n```bash\n# Run all tests with coverage report\npytest tests -v --cov=apple_search_ads --cov-report=term-missing\n\n# Run tests in parallel for faster execution\npytest tests -n auto\n\n# Generate HTML coverage report\npytest tests --cov=apple_search_ads --cov-report=html\n\n# Run integration tests (requires credentials)\npytest tests/test_integration.py -v\n```\n\nFor detailed testing documentation, see [TESTING.md](TESTING.md).\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- \ud83d\udc1b Issues: [GitHub Issues](https://github.com/bickster/apple-search-ads-python/issues)\n- \ud83d\udcd6 Documentation: [Read the Docs](https://apple-search-ads-python.readthedocs.io/)\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for a list of changes.\n\n## Acknowledgments\n\n- Apple for providing the Search Ads API\n- The Python community for excellent libraries used in this project\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Python client for Apple Search Ads API v5",
"version": "1.0.9",
"project_urls": {
"Documentation": "https://github.com/bickster/apple-search-ads-python/blob/main/README.md",
"Homepage": "https://github.com/bickster/apple-search-ads-python",
"Issues": "https://github.com/bickster/apple-search-ads-python/issues",
"Repository": "https://github.com/bickster/apple-search-ads-python"
},
"split_keywords": [
"apple",
" search",
" ads",
" api",
" marketing",
" advertising",
" ios",
" app",
" store"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "cc149cbcc7b08a35c888aa4ab3058637ae628100dbe74b6bd89885f0ef62a073",
"md5": "ecaa59296d54359d3b99fb024aa748fd",
"sha256": "3e2268c9013155d46e416e755fb27a1eb6d3aa86f959eca381ae433e330b0332"
},
"downloads": -1,
"filename": "apple_search_ads_client-1.0.9-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ecaa59296d54359d3b99fb024aa748fd",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 11615,
"upload_time": "2025-07-17T02:18:19",
"upload_time_iso_8601": "2025-07-17T02:18:19.990433Z",
"url": "https://files.pythonhosted.org/packages/cc/14/9cbcc7b08a35c888aa4ab3058637ae628100dbe74b6bd89885f0ef62a073/apple_search_ads_client-1.0.9-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "2326528bb3661b93698bcb077314d13b9bc337b22d0ab20542b04d2fe3625c6d",
"md5": "a3235199acf0d24b5b7fec0255901f58",
"sha256": "5c1e4d2ab3a37da95985b553cfde6dfedc05a288c361861f8c81afed52ee7df1"
},
"downloads": -1,
"filename": "apple_search_ads_client-1.0.9.tar.gz",
"has_sig": false,
"md5_digest": "a3235199acf0d24b5b7fec0255901f58",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 23504,
"upload_time": "2025-07-17T02:18:20",
"upload_time_iso_8601": "2025-07-17T02:18:20.877964Z",
"url": "https://files.pythonhosted.org/packages/23/26/528bb3661b93698bcb077314d13b9bc337b22d0ab20542b04d2fe3625c6d/apple_search_ads_client-1.0.9.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-17 02:18:20",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "bickster",
"github_project": "apple-search-ads-python",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "PyJWT",
"specs": [
[
">=",
"2.8.0"
]
]
},
{
"name": "cryptography",
"specs": [
[
">=",
"41.0.0"
]
]
},
{
"name": "requests",
"specs": [
[
">=",
"2.31.0"
]
]
},
{
"name": "pandas",
"specs": [
[
">=",
"2.0.0"
]
]
},
{
"name": "ratelimit",
"specs": [
[
">=",
"2.2.1"
]
]
},
{
"name": "python-dateutil",
"specs": [
[
">=",
"2.8.0"
]
]
}
],
"lcname": "apple-search-ads-client"
}