---
title: "Submit a cohort keyword query"
method: POST
path: "/cohorts/{cohortId}/queries"
tags: ["cohorts"]
---

# Submit a cohort keyword query

`POST /cohorts/{cohortId}/queries`

## Request

- Base URL: `https://www.socialcrawl.dev/v1`
- URL: `https://www.socialcrawl.dev/v1/cohorts/{cohortId}/queries`
- Auth: API key in header `x-api-key`

Submits an asynchronous keyword query over the cohort's recent authored activity. Returns 202 immediately with a poll URL. Credits are reserved at the worst-case ceiling on submission, metered one per successful upstream page, and the unspent remainder is refunded exactly once when the query reaches a terminal state.

## Path parameters

- `cohortId` string, required

## Headers

- `Idempotency-Key` string, uuid, required

## Request body

- object
  - `date_from` string, date-time, required: Start of the observation window as a full RFC3339 timestamp.
  - `date_to` string, date-time: Optional end of the observation window.
  - `keywords` string[], required: 1-20 unique keywords, matched literally after Unicode normalization and case folding.
  - `match` 'keywords' | 'topics' | 'both': Optional. `keywords` (the default) is the literal keyword match. `topics` checks every keyword match against your `topics` and returns only the ones that are about a topic, which removes homonyms (a fruit under the keyword `apple`). `both` checks every authored item and returns an item when a keyword matches OR it is about a topic, which adds posts that never use your keywords. `topics` and `both` require `topics`. If an item cannot be checked, the keyword match stands and its `matched_topics` is `null`.
  - `max_credits` integer, required: Your safety limit. Submission fails with 400 if the computed ceiling exceeds it; it is never permission to exceed the ceiling.
  - `max_items_per_identity` integer, required
  - `max_pages_per_identity` integer, required: Page budget per route. Exhausting it leaves `window_complete` false for that member.
  - `topics` object[]: Optional, 1-10 topics, used with `match` of `topics` or `both`. Each topic is your own `id` (letters, digits, `_` and `-`, up to 64 characters, echoed back in `matched_topics`) and a plain-language `description` of the thing to look for, up to 300 characters, in any language. Say which sense you mean: `Apple the technology company and its products`, not `apple`. Only the description and public post text are used for the check; identities and your external ids never are.
    - `description` string, required
    - `id` string, required
  - `platforms` string[]: Optional subset of the cohort's platforms to query. Defaults to every platform present in the cohort.

## Response `202`

Query accepted

- object
  - `cached` boolean
  - `credits_remaining` integer, nullable
  - `credits_used` integer
  - `data` object
    - `estimated_credits` integer
    - `member_count` integer
    - `progress` object
      - `matches` integer
      - `members_completed` integer
      - `members_total` integer
      - `pages_failed` integer
      - `pages_succeeded` integer
      - `routes_completed` integer
      - `routes_planned` integer
      - `shards_completed` integer
      - `shards_failed` integer
      - `shards_total` integer
    - `query_id` string
    - `replayed` boolean: True when this 202 replays an earlier submission via the same Idempotency-Key; the envelope also carries idempotent_replay and the X-Idempotent-Replay and Idempotent-Replayed response headers. A replay reports the query's CURRENT state, not necessarily queued.
    - `reserved_credits` integer
    - `result_url` string
    - `shard_count` integer
    - `status` 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'expired'
    - `status_url` string
  - `endpoint` string
  - `platform` string
  - `request_id` string
  - `success` boolean

## Other responses

- `400`: Invalid request - missing or malformed parameters
- `401`: Unauthorized - missing or invalid API key
- `402`: Payment required - the account balance is too low, or the calling key has spent its own per-key credit limit
- `404`: Not found - the endpoint does not exist, or the requested resource was not found upstream
- `405`: Method not allowed - wrong HTTP verb for this endpoint
- `409`: Conflict - an idempotent request is still in flight, its completed body exceeded the replay limit, or the cohort resource is not in a state that allows this operation
- `413`: Payload too large: the JSON request body exceeds the 1 MB size limit and is rejected before parsing, or a single cohort result cannot fit beneath the 1 MB response-page ceiling
- `422`: Idempotency payload mismatch - this Idempotency-Key was already used with a different request payload; or a discover request with async=false is over what one call can answer (NEEDS_ASYNC)
- `429`: Rate or concurrency limit exceeded. `RATE_LIMITED`: more than 600 requests in a 1-minute sliding window on this API key (headers `X-RateLimit-Limit`/`Remaining`/`Reset`; `Retry-After` is seconds until the window resets). `CONCURRENCY_LIMIT`: more than 50 simultaneous in-flight requests (headers `X-Concurrency-Limit`/`Remaining`; short static `Retry-After`). Both are unbilled. Honor `Retry-After`, then back off with jitter. See /docs/rate-limits.
- `500`: Internal server error - an immediate refund is attempted; credits_used reports the committed net charge and exact compensation is queued if that refund cannot commit

---

[API](https://skmtc.dev/socialcrawl/apis/socialcrawl-api.md) · [All operations](https://skmtc.dev/socialcrawl/apis/socialcrawl-api/llms.txt) · [OpenAPI document](https://skmtc.dev/socialcrawl/apis/socialcrawl-api/revisions/a0ad2511004a?raw)
