Skip to documentation

Developer reference

API reference

Read your app data and prepare listing changes through the AppDrift REST API. Check authentication, access and token costs for each endpoint.

Base URL

Base URL
https://appdrift-backend-1fabfc95f592.herokuapp.com

The OpenAPI specification describes the public /v1 endpoints. For agent setup, follow the MCP guide.

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 and during an eligible 7-day Basic trial. Pro and Studio start paid, with no free trial.

Request Header

Authorization: Bearer ad_live_your_key_here

API Endpoints

GET/v1/keyword-difficultyScore any keyword (free)
GET/v1/appsList connected apps (free)
GET/v1/apps/{id}/keywordsTracked keywords + ranks (free)
GET/v1/apps/{id}/opportunitiesPage-one candidates (free)
GET/v1/action-planWeekly ASO action plan (free)
GET/v1/mePlan + token balance (free)
POST/v1/apps/{id}/optimizeAI keyword optimize (3 tokens)
POST/v1/apps/{id}/optimize/applyApply proposal to draft (free)
POST/v1/generate-metadataGenerate ASO metadata (1-2 tokens/field)
POST/v1/translateTranslate a field (1-5 tokens/lang)
GET/webhooksList webhook endpoints (JWT)
POST/webhooksCreate webhook endpoint (JWT)
PATCH/webhooks/{id}Update webhook endpoint (JWT)
DELETE/webhooks/{id}Delete webhook endpoint (JWT)
POST/webhooks/{id}/testSend test delivery (JWT)
GET/webhooks/{id}/deliveriesLast 25 deliveries (JWT)

Reading your ASO data

GET/v1/keyword-difficultyScore any keyword — difficulty + popularity

Query Parameters

keywordRequired. The keyword or phrase to score.
platformios (default) or android
countryTwo-letter storefront code, default us

Response

{
  "error": null,
  "message": "Success",
  "data": {
    "keyword": "meditation",
    "platform": "ios",
    "country": "us",
    "difficulty": 63,
    "popularity": 41,
    "source": "live"
  }
}
GET/v1/appsList the apps connected to your account

Response

{
  "error": null,
  "message": "Success",
  "data": [
    {
      "id": 123,
      "name": "MyApp",
      "platform": "ios",
      "bundle_id": "com.example.myapp",
      "status": "active"
    }
  ]
}
GET/v1/apps/{id}/keywordsTracked keywords with their latest store rank
GET/v1/apps/{id}/opportunitiesPage-one push candidates (rank 11-30, easiest first)
GET/v1/action-planThis week's prioritized ASO actions across all apps
GET/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.

POST/v1/apps/{id}/optimizeAI-rewrite metadata toward a target keyword — 3 tokens

Request 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
  }
}
POST/v1/apps/{id}/optimize/applyWrite accepted fields into the app's draft version — free

Request 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.

POST/v1/generate-metadataGenerate ASO metadata from a brief — 1 token/short field, 2/long

Request 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
  }
}
POST/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 crossed
monitoring.alertStore monitoring alert
action_plan.readyWeekly Autopilot plan ready

Delivery: 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.

GET/webhooksList webhook endpoints

Response

{
  "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"
    }
  ]
}
POST/webhooksCreate webhook endpoint

Request 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"
  }
}
PATCH/webhooks/{id}Update webhook endpoint

Request 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}.

POST/webhooks/{id}/testSend test delivery

Response

{
  "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"
GET/webhooks/{id}/deliveriesLast 25 deliveries

Response

{
  "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.

400 Bad Request

Invalid parameters or body — the error string says which

401 Unauthorized

missing_api_key / invalid_api_key — bad or revoked key

402 Payment Required

insufficient_tokens / free_tokens_exhausted — top up or upgrade

404 Not Found

Resource doesn't exist or belongs to another account

409 Conflict

NO_DRAFT_VERSION on optimize/apply — create a draft version first

429 Too Many Requests

rate_limited — see Rate Limiting below

500 Internal Server Error

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.