---
title: "Adcash Campaign Management Guide"
description: "Complete guide to setting up and managing Adcash campaigns — from API access to optimization"
---

import { Steps, Tabs, Tab, Callout } from 'nextra/components'

# Adcash Campaign Management Guide

A practical walkthrough for getting Adcash campaigns running and keeping them profitable.

---

## 1. Prerequisites

### What You Need

- An **Adcash advertiser account** (web login credentials)
- An **API token** generated from the web panel
- A **Binom tracker** account (or any tracker with postback support)
- **Budget** — Adcash has a minimum budget per account (typically **$100**)

### Getting Your API Token

<Steps>
  ### Log into [Adcash Web Panel](https://adcash.myadcash.com/)
  Use your advertiser credentials.

  ### Navigate to Settings → API
  Find the API section in your account settings.

  ### Generate a new API token
  Copy it immediately — you won't see it again. Store it somewhere safe like a password manager.

  ### Test the token
  ```bash
  curl -s -X POST https://adcash.myadcash.com/client-api/v1/auth/token \
    -H "Content-Type: application/json" \
    -H "User-Agent: Mozilla/5.0" \
    -d '{"api_token": "YOUR_TOKEN_HERE"}' | python3 -m json.tool
  ```
  If successful, you'll get back an `access_token` (JWT) valid for 15 minutes.
</Steps>

<Callout type="warning">
  **Always include `User-Agent: Mozilla/5.0` in API requests.** The Adcash API rejects bare `curl` calls with HTTP 403.
</Callout>

---

## 2. Authentication (API Access)

### The Auth Flow

Adcash uses a two-step auth:
1. **API Token** → Exchange for a **JWT Access Token** (expires in 900 seconds / 15 min)
2. **JWT** → Used for all subsequent API calls

### Python Auth (Recommended)

```python
import urllib.request, json

# Read token from file (never hardcode!)
with open('/path/to/adcash_api_token.txt') as f:
    api_token = f.read().strip()

# Step 1: Exchange token for JWT
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/auth/token',
    data=json.dumps({'api_token': api_token}).encode(),
    headers={
        'Content-Type': 'application/json',
        'User-Agent': 'Mozilla/5.0'  # REQUIRED
    }
)
resp = json.loads(urllib.request.urlopen(req, timeout=15).read())
jwt = resp['data']['access_token']

# Step 2: Use JWT for subsequent requests
headers = {
    'Authorization': f'Bearer {jwt}',
    'User-Agent': 'Mozilla/5.0'
}

# Example: Check balance
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/advertisers/balance',
    headers=headers
)
print(json.loads(urllib.request.urlopen(req, timeout=15).read()))
```

<Callout type="important">
  **JWT expires after 15 minutes.** Generate a fresh token before each batch of operations. Reusing an expired token returns HTTP 401.
</Callout>

---

## 3. Campaign Setup — Step by Step

<Callout type="info">
  Campaign creation is complex with many required fields. Use the **PATCH method** to update existing campaigns rather than creating from scratch whenever possible.
</Callout>

### 3.1 List Existing Campaigns

```python
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns?limit=50',
    headers=headers
)
resp = json.loads(urllib.request.urlopen(req, timeout=15).read())
for c in resp['data']:
    print(f"{c['id']} | {c['name']} | {c['status']}")
```

### 3.2 Check Campaign Status

Before making changes, **always check current status first**:

```python
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns/459691718',
    headers=headers
)
r = json.loads(urllib.request.urlopen(req, timeout=15).read())
print(f"Status: {r['data']['status']}")  # PAUSED | RUNNING | ENDED
```

### 3.3 Start / Pause a Campaign

**Use PATCH, not /start or /resume:**

```python
# PATCH /campaigns — works for PAUSED ↔ RUNNING
data = json.dumps({'ids': [459691718], 'status': 'RUNNING'}).encode()
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns',
    data=data,
    headers=headers,
    method='PATCH'
)
raw = urllib.request.urlopen(req, timeout=15).read()
# 204 No Content = success
```

<Callout type="danger" title="Common Pitfall: PATCH Behavior">
  - **PAUSED → RUNNING**: Works (204)
  - **RUNNING → PAUSED**: Works (204)
  - **RUNNING → RUNNING**: Returns **HTTP 422** — "Campaign has already been started". Always check current status first.
  - **PAUSED → PAUSED**: Returns HTTP 204 (empty body). Handle empty response gracefully.
  - **ENDED → anything**: Returns HTTP 422 — ended campaigns cannot be restarted.
</Callout>

### 3.4 Set Budget

Each account has a **minimum budget**:

```python
# Check balance first
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/advertisers/balance',
    headers=headers
)
balance = json.loads(urllib.request.urlopen(req, timeout=15).read())
print(f"Balance: {balance['data']['balance']}")

# Set budget (minimum varies — commonly $100)
data = json.dumps({'amount': '100.00'}).encode()
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns/459691718/budget',
    data=data,
    headers=headers,
    method='POST'
)
urllib.request.urlopen(req, timeout=15)
```

<Callout type="warning">
  **$100 minimum** is common for Adcash accounts. Amounts below that return HTTP 422 — `"Amount must be no less than 100."`
</Callout>

### 3.5 Check Payouts (Bids & Goals)

Campaigns need a payout configuration — the type (CPA, CPM, CPC), bid amount, and conversion goal:

```python
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns/459691718/payouts',
    headers=headers
)
payouts = json.loads(urllib.request.urlopen(req, timeout=15).read())
for p in payouts['data']:
    print(f"Country: {p['country_code']} | Bid: {p['bid']} | Type: {p['payout_type_id']} | Goal: {p.get('goal_id')}")
```

---

## 4. Conversion Goals & Postbacks

<Callout type="error" title="Most Common Failure Point">
  **Wrong goal hash in postback URL** is the #1 reason Adcash campaigns don't convert. The goal ID set in the campaign payout must match the goal ID in the postback URL exactly.
</Callout>

### 4.1 Find Your Actual Goal

```python
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/goals',
    headers=headers
)
goals = json.loads(urllib.request.urlopen(req, timeout=15).read())
for g in goals['data']:
    print(f"ID: {g['id']} | Name: {g['name']} | Hash: {g.get('hash', 'N/A')}")
```

### 4.2 Postback URL Format

```
https://adcash.myadcash.com/client-api/v1/conversion?goal_id=HASH&click_id={externalid}
```

- `goal_id` = the goal's hash (e.g., `34002c`)
- `click_id` = the click ID from your tracker (`{externalid}` in Binom)

### 4.3 Check Which Goal Your Campaign Uses

The campaign payouts reference a goal. Compare this against your postback URL:

```python
payouts = json.loads(urllib.request.urlopen(
    f'https://adcash.myadcash.com/client-api/v1/campaigns/459691718/payouts',
    headers=headers
).read())
for p in payouts['data']:
    print(f"Goal ID in payout: {p.get('goal_id')}")
```

If the goal hash in your postback URL doesn't match the campaign's goal, **no conversions will track**.

### 4.4 Setting Postback in Binom

1. In Binom, go to **Traffic Sources** → Edit your Adcash traffic source
2. Set the **Postback URL** to:
   ```
   https://adcash.myadcash.com/client-api/v1/conversion?goal_id=YOUR_GOAL_HASH&click_id={externalid}
   ```
3. Replace `YOUR_GOAL_HASH` with the actual goal hash from step 4.1
4. Make sure the Goal ID and conversion event match

---

## 5. Creatives (Ad Formats)

### 5.1 List Creatives

```python
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns/459691718/creatives',
    headers=headers
)
creatives = json.loads(urllib.request.urlopen(req, timeout=15).read())
for c in creatives['data']:
    print(f"ID: {c['id']} | Type: {c['creative_type_id']} | URL: {c.get('url', 'N/A')}")
```

### 5.2 Creative Type IDs

| Type ID | Format |
|---------|--------|
| 40 | Pop-under |
| 41 | Interstitial |
| 42 | Banner |
| 43 | Video (in-stream) |
| 38 | In-page push |

### 5.3 Update Creative URL

```python
data = json.dumps({'url': 'https://your-new-landing-page.com'}).encode()
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns/459691718/creatives/12345',
    data=data,
    headers=headers,
    method='PATCH'
)
urllib.request.urlopen(req, timeout=15)
```

---

## 6. Targeting (Geo, Device, OS, etc.)

### 6.1 Check Current Targeting

```python
req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns/459691718/targetings',
    headers=headers
)
targetings = json.loads(urllib.request.urlopen(req, timeout=15).read())
for t in targetings['data']:
    print(f"Type: {t['type_id']} | Action: {t.get('action')} | Rules: {t.get('rules', [])}")
```

### 6.2 Add Zone Exclusions

Zone exclusions use targeting type **41** (Zone) with action **EXCLUDE**:

```python
data = json.dumps({
    'type_id': 41,
    'action': 'EXCLUDE',
    'rules': ['2714721', '10652968'],  # Zone IDs from tracker
    'country_code': None
}).encode()

req = urllib.request.Request(
    'https://adcash.myadcash.com/client-api/v1/campaigns/459691718/targetings',
    data=data,
    headers=headers,
    method='POST'
)
urllib.request.urlopen(req, timeout=15)
```

Zone IDs match your tracker's zone-level data. Use your tracker to identify high-spend, zero-conversion zones.

---

## 7. Zone Optimization (Waste Reduction)

### The Standard Threshold

| Spend | Action |
|-------|--------|
| >= $1.00, 0 conversions | **Exclude** immediately |
| $0.50 — $0.99, 0 conversions | **Monitor** — borderline |
| < $0.50, 0 conversions | **Leave alone** — free traffic |

### Workflow

1. Pull zone-level data from your tracker (group by zone ID)
2. For each country, find zones with high spend and no conversions
3. Exclude those zones via targeting (type 41, EXCLUDE)
4. Monitor campaign performance after exclusions

---

## 8. Common Errors & Solutions

| Error | Cause | Fix |
|-------|-------|-----|
| **HTTP 403** | Missing User-Agent header | Add `User-Agent: Mozilla/5.0` to all requests |
| **HTTP 401** | JWT expired | Generate fresh JWT (valid for 15 min) |
| **HTTP 422: "Campaign has already been started"** | PATCH to RUNNING when already RUNNING | Check status first with GET /campaigns/{id} |
| **HTTP 422: "Amount must be no less than 100"** | Budget below account minimum | Set budget >= $100 |
| **HTTP 422: "Campaign not found"** | Wrong campaign ID for this account | Campaign IDs are unique per account — verify you're using the right account |
| **HTTP 404: "Not Found" on /resume endpoint** | Using wrong endpoint | Use PATCH /campaigns instead |
| **No conversions tracking** | Goal hash mismatch | Compare postback URL goal_id with campaign payout goal_id |
| **"A wrong goal ID reported"** | Postback goal hash ≠ actual goal | Update postback URL with correct hash from GET /goals |
| **Third-party postback (imcounting.com etc.)** | Postback goes to third-party instead of Adcash | Update Binom traffic source postback to direct Adcash URL |

---

## 9. Account-Specific Notes

### Multiple Accounts

Campaign IDs are **NOT shared across accounts**. Campaign **459691718** belongs to one account. The same ID may not exist in another account.

| Account | Email | API Token Location |
|---------|-------|-------------------|
| Account A | user1@example.com | `~/.hermes/secrets/adcash_account_a_token.txt` |
| Account B | user2@example.com | `~/.hermes/secrets/adcash_account_b_token.txt` |

### Known Minimum Budgets

- **Account A:** $100 minimum
- **Account B:** Varies — check via `GET /advertisers/balance`

---

## 10. Quick Reference (API Endpoints)

### Auth
```
POST /auth/token
```
Body: `{"api_token": "..."}`
Response: `{"data": {"access_token": "..."}}`

### Campaigns
```
GET    /campaigns                    — List (paginated)
GET    /campaigns/{id}               — Single campaign
PATCH  /campaigns                    — Bulk status update
                                        Body: {"ids": [1,2], "status": "PAUSED"}
                                        Statuses: PAUSED, RUNNING, ENDED
```

### Budget
```
GET    /campaigns/{id}/budget        — Get current budget
POST   /campaigns/{id}/budget        — Create/update budget
                                        Body: {"amount": "100.00"}
DELETE /campaigns/{id}/budget        — Remove budget
```

### Creatives
```
GET    /campaigns/{id}/creatives                — List
POST   /campaigns/{id}/creatives                — Create
PATCH  /campaigns/{id}/creatives/{creativeId}   — Update URL/status
DELETE /campaigns/{id}/creatives/{creativeId}   — Delete
```

### Payouts
```
GET    /campaigns/{id}/payouts                  — List
DELETE /campaigns/{id}/payouts/{payoutId}       — Delete
```

### Targeting
```
GET    /campaigns/{id}/targetings               — List
POST   /campaigns/{id}/targetings               — Create
PATCH  /campaigns/{id}/targetings/{id}          — Update
DELETE /campaigns/{id}/targetings/{id}          — Delete
```

### Goals
```
GET    /goals                           — List all goals
GET    /goals/{id}                      — Single goal
```

### Reference Data
```
GET /collections/countries           — Country codes
GET /collections/creative-types      — Type IDs (pop=40, interstitial=41, etc.)
GET /collections/payout-types        — Type IDs (CPM=1, CPC=2, CPA=6)
GET /collections/targeting-types     — Targeting type IDs
GET /collections/browsers            — Browser list
GET /collections/device-types        — Device types
GET /collections/operating-systems   — OS list
GET /collections/languages           — Language codes
GET /collections/supply-sources      — Supply sources
```

---

## 11. Full Python Script Template

```python
#!/usr/bin/env python3
"""Adcash campaign management helper."""

import urllib.request, json

API_TOKEN_PATH = '/path/to/your/api_token.txt'
BASE = 'https://adcash.myadcash.com/client-api/v1'

def get_jwt():
    with open(API_TOKEN_PATH) as f:
        api_token = f.read().strip()

    req = urllib.request.Request(
        f'{BASE}/auth/token',
        data=json.dumps({'api_token': api_token}).encode(),
        headers={'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0'}
    )
    resp = json.loads(urllib.request.urlopen(req, timeout=15).read())
    return resp['data']['access_token']

def api(method, path, data=None):
    jwt = get_jwt()  # Fresh JWT on every call
    h = {'Authorization': f'Bearer {jwt}', 'User-Agent': 'Mozilla/5.0'}
    
    req = urllib.request.Request(
        f'{BASE}{path}',
        data=json.dumps(data).encode() if data else None,
        headers=h,
        method=method
    )
    
    try:
        raw = urllib.request.urlopen(req, timeout=15).read()
        return json.loads(raw) if raw else None
    except urllib.error.HTTPError as e:
        print(f"Error {e.code}: {e.read().decode()[:200]}")
        return None

# === Usage Examples ===

# List campaigns
# result = api('GET', '/campaigns?limit=10')

# Check campaign
# result = api('GET', '/campaigns/459691718')

# Pause campaign
# result = api('PATCH', '/campaigns', {'ids': [459691718], 'status': 'PAUSED'})

# Resume campaign
# result = api('PATCH', '/campaigns', {'ids': [459691718], 'status': 'RUNNING'})

# Check balance
# result = api('GET', '/advertisers/balance')
```

---

## 12. Troubleshooting Checklist

If a campaign isn't working, go through this list in order:

- [ ] **Can you authenticate?** Test your API token with `POST /auth/token`
- [ ] **Is the campaign RUNNING?** `GET /campaigns/{id}` → check `status`
- [ ] **Is there a budget?** `GET /campaigns/{id}/budget`
- [ ] **Are there creatives?** `GET /campaigns/{id}/creatives`
- [ ] **Are the creatives approved?** Check in web panel
- [ ] **Is the creative URL correct?** The landing page should load
- [ ] **Are payouts configured?** `GET /campaigns/{id}/payouts`
- [ ] **Is the goal hash correct?** Compare `GET /goals` with your postback URL
- [ ] **Is the postback URL set in your tracker?** Check traffic source settings
- [ ] **Is the postback format correct?** Should be `/conversion?goal_id=HASH&click_id={externalid}`
- [ ] **Are targeting rules correct?** Not accidentally excluding all traffic
- [ ] **Check web panel for errors** — Adcash shows specific error messages there
