# Tag ASO keywords

Put one tag on a set of tracked keywords, or lift it from them.

## Contract status

Preview contract: these routes and tools are implemented in the shared contract, but the public host, customer API access and OAuth flow have not been deployment verified.

## Endpoint: POST /v1/aso/apps/{appId}/keywords/tags

- Method: `POST`
- Path: `/v1/aso/apps/{appId}/keywords/tags`
- Authentication: Bearer API credential or OAuth access token
- Required scope: `content:write`
- MCP tool: `assign_aso_keyword_tag`

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `appId` | path | uuid | yes | Tracked ASO app id. |
| `tagId` | body | uuid | yes | Keyword tag to apply or lift. |
| `keywordIds` | body | uuid[] | yes | Tracked keywords to change, all belonging to this app. |
| `action` | body | 'add' | 'remove' | yes | Apply the tag or lift it. |

## Known errors

- 400 invalid_request for an invalid filter, identity or limit.
- 401 authentication_required for a missing or invalid credential.
- 402 subscription_required when access is not entitled.
- 403 authorization_required for a missing scope or unavailable preview access.
- 404 not_found when the record is outside the accessible catalogue.
- 429 rate_limited; honor Retry-After when present.
- 409 conflict when the write would duplicate an exclusive row.

## Request

One command changes the whole set. Applying a tag a keyword already carries changes nothing, so a retry is safe. A keyword id belonging to another app or another owner is rejected outright rather than skipped.

```
curl --request POST 'https://api.peekpanda.com/v1/aso/apps/11111111-1111-4111-8111-111111111111/keywords/tags' \
  --header "Authorization: Bearer $PEEKPANDA_TOKEN" \
  --header 'x-genviral-tenancy: personal' \
  --header 'content-type: application/json' \
  --data '{"tagId":"55555555-5555-4555-8555-555555555555","keywordIds":["22222222-2222-4222-8222-222222222222"],"action":"add"}'
```

## Response

The updated workstation.

```
{
  "app": {
    "storeId": "0000000000",
    "name": "Example Calorie Tracker",
    "iconUrl": null,
    "developerName": "Example Labs",
    "primaryGenre": "Health & Fitness",
    "rating": 4.7,
    "ratingCount": 12450,
    "inCatalogue": true,
    "id": "11111111-1111-4111-8111-111111111111",
    "kind": "apple",
    "folderId": null,
    "role": "mine",
    "createdAt": "2026-09-06T00:00:00.000Z",
    "lastRefreshedAt": "2026-09-06T00:00:00.000Z",
    "keywordCount": 1
  },
  "refresh": {
    "state": "fresh",
    "lastRefreshedAt": "2026-09-06T00:00:00.000Z",
    "nextAllowedAt": "2026-09-06T01:00:00.000Z",
    "staleQueryCount": 0,
    "activeJobId": null,
    "budget": {
      "userRefreshesToday": 1,
      "userDailyLimit": 40
    }
  },
  "profile": {
    "positionKind": "observed_search_api_position",
    "storeId": "0000000000",
    "coverage": {
      "queriesCollected": 0,
      "queriesMatched": 0,
      "country": "us",
      "language": "en_us",
      "source": "itunes_search",
      "latestObservedAt": null,
      "observationCount": 0
    },
    "summary": {
      "top3": 0,
      "top10": 0,
      "top50": 0,
      "bestPosition": null,
      "medianPosition": null
    },
    "keywords": [],
    "rivals": [],
    "keywordIdeas": [],
    "insights": [
      {
        "kind": "coverage_gap",
        "title": "Not in the collected query set yet",
        "detail": "This app did not appear in any collected queries.",
        "keywords": [],
        "catalogueStoreId": null
      }
    ]
  },
  "keywords": [
    {
      "query": "calorie tracker",
      "position": 4,
      "trackedKeywordId": "22222222-2222-4222-8222-222222222222",
      "source": "manual",
      "note": null,
      "tags": [
        {
          "id": "55555555-5555-4555-8555-555555555555",
          "name": "Potential",
          "color": "#0f9d58",
          "createdAt": "2026-09-06T00:00:00.000Z",
          "updatedAt": "2026-09-06T00:00:00.000Z"
        }
      ],
      "observedAt": "2026-09-06T00:00:00.000Z",
      "isStale": false,
      "resultCount": 1,
      "apps": [
        {
          "storeId": "0000000000",
          "position": 4,
          "name": "Example Calorie Tracker",
          "isSubject": true,
          "inCatalogue": true
        }
      ],
      "history": [
        {
          "observedAt": "2026-09-06T00:00:00.000Z",
          "position": 4
        }
      ],
      "difficulty": {
        "score": 40
      },
      "demand": {
        "score": 90,
        "observedAt": "2026-09-06T00:00:00.000Z"
      }
    }
  ],
  "suggestions": [
    {
      "query": "calorie counter",
      "source": "hint",
      "reason": "Apple suggests it",
      "position": null,
      "appCount": null,
      "isTracked": false
    }
  ],
  "tags": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "name": "Potential",
      "color": "#0f9d58",
      "createdAt": "2026-09-06T00:00:00.000Z",
      "updatedAt": "2026-09-06T00:00:00.000Z"
    }
  ],
  "trackedCompetitors": []
}

```

## Source

Dated public App Store search results and suggestion observations for the US English storefront. Positions are observed result order. Difficulty and demand are directional indicators, not search volume or advertising data.

## Refresh

This read returns stored observations. Use the related refresh command when newer evidence is needed.
