Base URL
https://appdrift-backend-1fabfc95f592.herokuapp.comA machine-readable OpenAPI 3.1 spec of the /v1 surface lives at /.well-known/openapi.json.
Authentication
All /v1 requests authenticate with an AppDrift API access key (ad_live_…) as a Bearer token. Create keys on the Developers page — the full key is shown exactly once; we store only a hash. API access is included on every paid plan (7-day free trial).
Request Header
Authorization: Bearer ad_live_your_key_hereAPI Endpoints
/v1/keyword-difficultyScore any keyword (free)/v1/appsList connected apps (free)/v1/apps/{id}/keywordsTracked keywords + ranks (free)/v1/apps/{id}/opportunitiesPage-one candidates (free)/v1/action-planWeekly ASO action plan (free)/v1/mePlan + token balance (free)/v1/apps/{id}/optimizeAI keyword optimize (3 tokens)/v1/apps/{id}/optimize/applyApply proposal to draft (free)/v1/generate-metadataGenerate ASO metadata (1-2 tokens/field)/v1/translateTranslate a field (1-5 tokens/lang)/webhooksList webhook endpoints (JWT)/webhooksCreate webhook endpoint (JWT)/webhooks/{id}Update webhook endpoint (JWT)/webhooks/{id}Delete webhook endpoint (JWT)/webhooks/{id}/testSend test delivery (JWT)/webhooks/{id}/deliveriesLast 25 deliveries (JWT)Reading your ASO data
/v1/keyword-difficultyScore any keyword — difficulty + popularityQuery Parameters
keywordRequired. The keyword or phrase to score.platformios (default) or androidcountryTwo-letter storefront code, default usResponse
{
"error": null,
"message": "Success",
"data": {
"keyword": "meditation",
"platform": "ios",
"country": "us",
"difficulty": 63,
"popularity": 41,
"source": "live"
}
}/v1/appsList the apps connected to your accountResponse
{
"error": null,
"message": "Success",
"data": [
{
"id": 123,
"name": "MyApp",
"platform": "ios",
"bundle_id": "com.example.myapp",
"status": "active"
}
]
}/v1/apps/{id}/keywordsTracked keywords with their latest store rank/v1/apps/{id}/opportunitiesPage-one push candidates (rank 11-30, easiest first)/v1/action-planThis week's prioritized ASO actions across all apps/v1/meKey introspection — plan name + remaining token balance{
"error": null,
"message": "Success",
"data": { "plan": "Pro", "tokens_remaining": 2841 }
}AI actions
These consume plan tokens at the same prices as the dashboard and only charge on success — a failed call never charges. They return proposals or write to your app's draft version inside AppDrift; nothing reaches the App Store or Play Store without your normal review → publish flow. Check affordability first with GET /v1/me.
/v1/apps/{id}/optimizeAI-rewrite metadata toward a target keyword — 3 tokensRequest Body
{
"keyword": "habit tracker",
"country": "us"
}Response
{
"error": null,
"message": "Success",
"data": {
"platform": "ios",
"keyword": "habit tracker",
"current": { "title": "Drift", "subtitle": "Simple routines", "keywords": "..." },
"proposed": { "title": "Drift: Habit Tracker", "subtitle": "Daily streaks & reminders", "keywords": "..." },
"rationale": "…why these changes target the keyword…",
"tokens_charged": 3
}
}/v1/apps/{id}/optimize/applyWrite accepted fields into the app's draft version — freeRequest Body
{
"fields": { "subtitle": "Daily streaks & reminders" }
}Responds 409 NO_DRAFT_VERSION if the app has no editable draft version yet — create one in the dashboard first.
/v1/generate-metadataGenerate ASO metadata from a brief — 1 token/short field, 2/longRequest Body
{
"platform": "ios",
"app_name": "Drift",
"app_description": "A minimalist habit tracker with streaks and smart reminders.",
"fields": ["subtitle", "keywords"],
"language": "en-US",
"keywords": ["habit tracker", "daily routine"]
}iOS fields: name, subtitle, promotional_text, description, keywords. Android: title, short_description, full_description. Omit fields to generate all.
Response
{
"error": null,
"message": "Success",
"data": {
"platform": "ios",
"language": "en-US",
"fields": {
"subtitle": "Build habits that stick",
"keywords": "habit,tracker,routine,streak,daily,goals,productivity"
},
"tokens_charged": 2
}
}/v1/translateTranslate one field into up to 15 languages — 1 token/lang (3-5 long)Request Body
{
"field": "subtitle",
"text": "Build habits that stick",
"target_languages": ["de-DE", "ja", "es-MX"],
"source_language": "en-US"
}Fields: title, subtitle, description (3 tokens/lang), whats_new, promotional_text, android_title, short_description, full_description (5 tokens/lang), recent_changes.
Response
{
"error": null,
"message": "Success",
"data": {
"field": "subtitle",
"translations": {
"de-DE": "Gewohnheiten, die bleiben",
"ja": "続く習慣づくり",
"es-MX": "Hábitos que perduran"
},
"tokens_charged": 3
}
}Webhooks
Outbound webhooks push events to your own endpoints as they happen. Available on every paid plan (Free plans receive a 403). Unlike the /v1 endpoints above, these management routes authenticate with your dashboard session token (the JWT issued at login) — not an ad_live_… API key. The easiest way to manage endpoints is the Developers page in your dashboard. Up to 5 endpoints per account. Each endpoint gets an HMAC signing secret shown exactly once at creation — every delivery carries an X-AppDrift-Signature header (t=<unix>,v1=<hex>, HMAC-SHA256 of `${t}.${rawBody}`).
Events
keyword.rank_changeKeyword rank threshold crossedmonitoring.alertStore monitoring alertaction_plan.readyWeekly Autopilot plan readyDelivery: 4 attempts per event — immediate, then +1m, +10m, +60m. After 20 consecutive failures the endpoint is auto-disabled; re-enable it from the dashboard or via PATCH.
/webhooksList webhook endpointsResponse
{
"error": null,
"message": "Success",
"data": [
{
"id": 1,
"url": "https://example.com/webhooks/appdrift",
"events": ["keyword.rank_change", "monitoring.alert"],
"secret_suffix": "9d0e1f2a",
"enabled": true,
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": "2026-08-03T09:00:00Z",
"last_failure_at": null,
"created_at": "2026-08-01T10:00:00Z"
}
]
}/webhooksCreate webhook endpointRequest Body
{
"url": "https://example.com/webhooks/appdrift",
"events": ["keyword.rank_change", "monitoring.alert", "action_plan.ready"]
}Response (201)
Includes the full row plus the plaintext signing secret — returned exactly once, never shown again.
{
"error": null,
"message": "Store this signing secret now — it won't be shown again.",
"data": {
"id": 1,
"url": "https://example.com/webhooks/appdrift",
"events": ["keyword.rank_change", "monitoring.alert", "action_plan.ready"],
"secret": "whsec_9f2c1a7d3b4e5f6a7b8c9d0e1f2a3b4c",
"secret_suffix": "1f2a3b4c",
"enabled": true,
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"created_at": "2026-08-01T10:00:00Z"
}
}/webhooks/{id}Update webhook endpointRequest Body (all fields optional)
{
"url": "https://example.com/hooks/new-path",
"events": ["action_plan.ready"],
"enabled": true
}Setting enabled: true re-enables an auto-disabled endpoint. Delete an endpoint with DELETE /webhooks/{id}.
/webhooks/{id}/testSend test deliveryResponse
{
"error": null,
"message": "Success",
"data": { "ok": true, "status": 200 }
}
// The test delivery is a signed event of type "test.ping" — if your
// receiver validates event types, accept it alongside the three real events.
// On failure, data carries a coarse error instead of a status:
// "data": { "ok": false, "error": "timed_out" } // or "connection_failed",
// // "endpoint_disabled", "HTTP 4xx/5xx"/webhooks/{id}/deliveriesLast 25 deliveriesResponse
{
"error": null,
"message": "Success",
"data": [
{
"event_type": "keyword.rank_change",
"status": "delivered",
"attempts": 1,
"response_status": 200,
"last_error": null,
"created_at": "2026-08-03T09:00:00Z",
"delivered_at": "2026-08-03T09:00:01Z"
},
{
"event_type": "monitoring.alert",
"status": "failed",
"attempts": 4,
"response_status": 500,
"last_error": "HTTP 500",
"created_at": "2026-08-02T18:00:00Z",
"delivered_at": null
}
]
}status is one of pending, delivered, or failed.
Error Responses
Every response — success or failure — uses the same envelope: { "error", "message", "data" }. On failure, error carries a short machine-readable string.
Invalid parameters or body — the error string says which
missing_api_key / invalid_api_key — bad or revoked key
insufficient_tokens / free_tokens_exhausted — top up or upgrade
Resource doesn't exist or belongs to another account
NO_DRAFT_VERSION on optimize/apply — create a draft version first
rate_limited — see Rate Limiting below
Something failed on our side — AI endpoints never charge on failure
Error Response Format
{
"error": "insufficient_tokens",
"message": "This call costs 3 token(s); you have 1 remaining.",
"data": { "required": 3, "remaining": 1 }
}Rate Limiting
API Rate Limits
Per API key
300 requests / 15 min
AI endpoints, per key
30 requests / 15 min
Standard RateLimit-Limit,RateLimit-Remaining, andRateLimit-Reset headers are returned on every response. A coarse per-IP backstop (500 / 15 min) also applies.