# POST /v1/keywords/difficulty

Keyword difficulty (0-100) per keyword; unscored keywords return pending and are scored for a later call.

**Credits:** 110 credits per call.

**Timeout:** 120s

## Parameters

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `keywords` | array | yes | — | Keywords to score difficulty for. Required, non-empty. Array of string. Max 500 items. |
| `location` | any | yes | — | Google Ads geotarget ID, or a location name (e.g. a country/state/city name). Required. Look one up with /v1/locations. |
| `language` | string | yes | — | ISO language code. Required. |
| `tag` | string | no | — | Optional client-defined label forwarded to the upstream task for your own bookkeeping; not echoed back in the response. |

## Request

```http
POST /v1/keywords/difficulty HTTP/1.1
Authorization: Bearer sof_live_your_key_here
Content-Type: application/json

{
  "keywords": [
    "buy running shoes",
    "cold keyword"
  ],
  "location": 2840,
  "language": "en"
}
```

## Response

Wrapped in the standard envelope (id, object, created_at, elapsed_ms, cache, credits) -- documented once on The Contract. The body below is a real envelope with this endpoint's `data` shape; values vary per request, and a `…` marks an array cut short for display.

```json
{
  "id": "keyw_mhgrsciz57rca2ci25y6v2da",
  "request_id": "req_dsi6envu6rhvndkg5lev6soj44",
  "object": "keyword_difficulty",
  "created_at": "2026-08-09T08:30:31Z",
  "elapsed_ms": 180,
  "cache": "miss",
  "credits": {
    "charged": 110,
    "balance": 9857
  },
  "data": {
    "items_count": 2,
    "items": [
      {
        "keyword": "buy running shoes",
        "difficulty": 42,
        "status": "available"
      },
      {
        "keyword": "cold keyword",
        "difficulty": null,
        "status": "pending"
      }
    ]
  }
}
```

## Response fields

What each field in `data` (above) means.

| Field | Description |
| --- | --- |
| `data.items_count` | Number of entries in items. |
| `data.items` | One row per requested keyword. |
| `data.items[].keyword` | The keyword. |
| `data.items[].difficulty` | 0-100 difficulty score, or null while status is "pending". |
| `data.items[].status` | "available" once scored, or "pending" if the keyword needed a fresh scrape that hadn't completed yet -- retry the same keyword later to resolve it. |
