Google Ads on autopilot. By API.
Read live campaign state, surface AI recommendations, kill waste keywords, and push mutations to your clients' accounts — all over plain REST + JSON. The same engine that runs ShuiLink-managed accounts, exposed to your tools.
🛠 What you can build
A few patterns we built this for. If your idea isn't here, email us.
White-label dashboards
Agencies showing real-time campaign health to clients without touching the Google Ads UI.
AI optimization bots
Background jobs that pull search terms, find waste, and push negative keywords every night.
Custom reporting
Pipe campaign metrics into your data warehouse, Slack, or finance system.
Spend alerting
"Notify me when CPC for hibachi keywords exceeds $3" — without writing GAQL.
A/B test orchestration
Programmatically rotate RSA headlines or pause underperforming ad groups.
Multi-client roll-ups
One key, all your client accounts, one summary view — saves the MCC switching dance.
🚀 Quickstart
Get your first response in under 60 seconds.
Create an account
Sign up with email + password or Google. New accounts land in pending-approval and need admin promotion before they can call the API.
Sign up →Generate an API key
Go to the Keys page → click + Create key → pick a tier → copy the raw key. It's only shown once, so save it somewhere safe.
sl_e332f39d5878f54d140b6ee1537fdfe8f7906cec53ab92f6b8c22301634bda80 └─┴── 64 hex characters ──────────────────────────────────────────────┘
Make your first call
List all campaigns across your authorized clients:
curl https://ads.shuilink.com/api/v1/campaigns/ \ -H "Authorization: Bearer sl_YOUR_KEY"
🔐 Authentication
All API requests must include your API key in the Authorization header.
Authorization: Bearer sl_e332f39d5878...
Alternative headers accepted: X-Api-Key: sl_.... Avoid query params
(?api_key=) — they leak into server logs and browser history.
Keys are 67-character strings prefixed with sl_. We store only the SHA-256 hash —
if you lose a key, you must revoke and generate a new one.
📊 Rate limits
Limits are enforced per-key. Every response carries headers so you can self-throttle.
| Tier | Daily limit | Per-minute | Max keys | Best for |
|---|---|---|---|---|
| Free | 100 req / day | 10 req / min | 2 active | Try it out, prototypes |
| Pro | 10,000 req / day | 100 req / min | 5 active | Production agencies, 5–20 clients |
| Enterprise | Unlimited | Unlimited | 20 active | White-label, SLA, dedicated support |
Response headers
Every successful response includes these. Watch X-RateLimit-Remaining to back off before you 429.
X-Tier: pro X-RateLimit-Limit: 10000 X-RateLimit-Remaining: 9837
⚠️ Errors
All errors return JSON with a single error field describing the problem.
| Status | When | What to do |
|---|---|---|
| 401 | Missing or invalid API key | Check Authorization header. Key revoked? Generate a new one. |
| 403 | Key valid but account pending approval | Email shuilin9108@gmail.com to request access. |
| 405 | Wrong HTTP method (e.g. POST to GET endpoint) | Check the endpoint reference below. |
| 422 | Tier max-keys reached on create | Revoke an old key or upgrade tier. |
| 429 | Daily quota exceeded | Wait until UTC midnight reset, or upgrade tier. |
| 500 | Upstream Google Ads API error | Retry with exponential backoff. Status persists → email us. |
Example 429 body:
{
"error": "Daily quota of 100 requests reached for tier free.",
"reason": "DAILY_QUOTA_EXCEEDED"
}
📣 Campaigns
Read live campaign state across all clients your key is authorized for.
Returns every non-removed campaign for every client this key has access to. Useful as a daily "is anything on fire" check or to seed a dashboard.
Query parameters
| (none in v1 — filtering coming in v1.1) |
Example response
{
"version": "v1",
"fetched_at": "2026-05-25T00:34:06.105Z",
"clients": [
{
"client_id": "kobe-hibachi",
"display_name": "Kobe Hibachi",
"customer_id": "4384756043",
"campaigns": [
{
"id": "23755951174",
"name": "Kobe Hibachi NYC Core",
"status": "PAUSED",
"channel": "PERFORMANCE_MAX",
"bidding": "MAXIMIZE_CONVERSIONS"
}
]
}
]
}
Detail view — campaign + budget + 7d spend, clicks, conv.
Example response
{
"id": "23759542519",
"name": "NYC Hibachi - Search - Core",
"client_id": "kobe-hibachi",
"status": "PAUSED",
"channel": "SEARCH",
"bidding": "TARGET_SPEND",
"budget_usd": 80,
"metrics_7d": { "cost": 142.55, "clicks": 38, "conv": 0 }
}
🎯 Ad groups
Inspect ad groups, bids, and per-group performance.
[
{ "id": "182936...", "name": "Core - Hibachi at home",
"status": "ENABLED", "bid_usd": 3.50,
"cost_7d": 95.10, "clicks_7d": 27, "conv_7d": 0 }
]
🔑 Keywords
Read keyword + match type + per-keyword spend.
Query parameters
| limit | number | Default 10, max 100 |
| order | string | 'cost' (default) | 'clicks' | 'conv' |
🔍 Search terms
The actual queries users typed that triggered your ads — where the waste hides.
Use case: filter where cost ≥ $5 AND conv = 0 and
POST those to /v1/mutations/negative-keywords.
🤖 AI recommendations
Where the magic lives. AI-generated suggestions for keywords, headlines, budgets.
[
{ "type": "ADD_NEGATIVE_KEYWORD", "severity": "high",
"client_id": "kobe-hibachi", "campaign_id": "23759...",
"evidence": { "term": "private chef nyc", "cost_30d": 24.50, "conv": 0 },
"suggested_action": "Add 'private chef' as broad negative" },
{ "type": "RSA_HEADLINE_REWRITE", "severity": "medium",
"client_id": "a1-hibachi", "ad_group_id": "182...",
"current": "Premium Hibachi Catering",
"suggested": "Book a Chef for Your Backyard - From $399" }
]
✏️ Mutations
Push changes back to Google Ads — pause campaigns, add negatives, update bids. Pro+ only.
Body
{ "customer_id": "4384756043", "campaign_id": "23759542519" }
Body
{
"customer_id": "4384756043",
"campaign_id": "23759542519",
"negatives": [
{ "text": "private chef", "match_type": "BROAD" },
{ "text": "free hibachi", "match_type": "PHRASE" }
]
}
Ready to start automating?
Free tier doesn't need a credit card. You'll have a working API key in 60 seconds.
Sign up free →