Skip to main content
POST /v1/instagram/posts/search finds individual posts. Send one post block with a semantic query, structured filters, nested author filters, or any combination. This page is the whole endpoint: querying, filtering, ordering, and how to read what comes back.
Search posts finds content that matches. To find people that match, use Search accounts.

Query the content

post.query is a natural-language description of the content you want. Klint evaluates it against everything it observes about a post: caption, spoken transcript, on-screen text, visual description, and creative interpretation. The caption is only one source. Describe the content; don’t guess where the evidence lives.
Two promises behind that response:
  • Every result qualifies. A returned post satisfies the full query and every filter. Klint does not pad weak nearest-neighbors into the tail. A short result list means what Klint tracks held that many matches, not that ranking degraded.
  • Every result is a complete post summary at fixed paths: the same fields for every result, with null where Klint can’t currently support a value. See reading the results.

Writing a query that qualifies

Write post.query as a description of the content you want, not a keyword list. The retrieval engine reads meaning, so state form and substance: Two habits improve results:
  • Move hard constraints into filters. Dates, formats, languages, hashtags, and metrics are exact, so filters on them are exact. Keep the query for what only interpretation can establish.
  • Don’t force exhaustiveness. Results come from what Klint has observed, ranked. An empty result means Klint observed no match, never proof of absence on Instagram.
Klint’s classifications improve retrieval but never bound it. A good description still finds content that no catalog value describes yet.

Filter the results

Filters are hard constraints: Klint returns a post only if it satisfies every filter. Every filter value is an operator object; there is no bare-value shorthand. "format": "reel" is rejected; "format": { "in": ["reel"] } is correct.

Operators

Rules that follow from the shapes:
  • A field takes exactly one operator. contains_any and contains_all never combine on the same field.
  • Membership arrays are non-empty and hold unique values.
  • Ranges need at least one bound and allow at most one lower (gt/gte) and one upper (lt/lte). Klint rejects contradictory bounds.
  • Boolean filters are the direct values true or false, not operator objects.

How filters combine

Different fields AND together. No cross-field OR, no negation (not, exclude, none), no exists operator, no expression language. The grammar is deliberately closed and positive-only.
null never matches. A missing, hidden, or not-yet-published value satisfies no filter: no range, no membership, no boolean in either direction. has_paid_partnership_label: false matches only affirmatively observed absence, never unknown state. “Unknown” is not expressible in the launch grammar.

Value normalization

One rule governs what Klint fixes for you: Klint silently normalizes a value only when the intent is mechanically unambiguous; everything else gets a teaching error.
  • Hashtags are written without # ("doggrooming"). Klint accepts a leading # and normalizes it away.
  • Caption mentions are written without @, lowercase. Klint accepts a leading @ and normalizes it away.
  • Languages use BCP 47 tags with basic range matching: primary_language for "en" matches en, en-US, and en-GB; a more specific "en-GB" stays specific.
  • Classification IDs (topics, creative_formats, hook.devices, commercial_contexts) are exact catalog IDs, never display labels. Klint does not fuzzy-resolve "Dog Grooming" to dog_grooming. It rejects an unrecognized ID with suggestions (see what gets rejected).
  • Timestamps are RFC 3339 date-times.

Post filter fields

Not filterable at launch:
  • Raw caption, transcript, or on-screen text (use the semantic query for content intent)
  • Media duration
  • Coauthor or paid-partner identity
  • Key messages, proofs, offers, or calls to action
  • Narrative structure, brands, or products
This is the launch candidate field list. Each field must still pass its source, freshness, and precision gates. Klint removes a field that fails; it does not silently weaken it. Expect the list to shrink before it grows.

Author filters

The nested post.author.filters block constrains who published the post. Post criteria and author criteria qualify independently. A strong content match never compensates for an author outside your follower range. Author filters are structural only; there is no author.query in the launch contract. These are the same account fields Search accounts uses as its primary filters. Skip query to define a structural population. Add sort to order it:

What gets rejected

The difference between an empty result and a rejected request matters to an agent. A non-matching filter is a valid request that returns data: []. An invalid filter never executes; it returns a 422 validation-failed problem object that names the exact pointer, reason, and correction.
Valid requests, empty or reduced results, no error:
  • "hashtags": { "contains_all": ["groomingtips", "deshedding"] } against a post carrying only groomingtips: contains_all needs every value.
  • "has_paid_partnership_label": false against a post whose label state is null: unknown is not false.
  • "views": { "gte": 100000 } against a carousel whose views is null: a missing count never satisfies a range, no matter how many likes the post has.
  • "hook.devices": { "contains_any": ["problem_callout"] } against a post whose hook has devices: []: an empty classification can’t satisfy a device filter (the post stays findable by semantic query).
Bare value instead of an operator object:
sort on a query-bearing request: a query owns its ordering; sort is only for filters-only requests:
Both collection operators on one field:
Contradictory range bounds:
Unknown filter field: the grammar is closed; fields you may know from other APIs (like shares) don’t exist here:
Negation attempt: there is no not_in, exclude, or none:
Unknown or retired classification ID: Klint rejects a classification field (topics, creative_formats, hook.devices, commercial_contexts) whose ID isn’t in the catalog. The rejection carries reason INVALID and, when close IDs exist, a candidates list to correct from. Klint never lets a hallucinated ID evaluate silently to an empty result. data: [] always means “what Klint tracks has no match,” never “you invented that ID.” Klint rejects a valid catalog ID that is not filter-enabled with reason UNSUPPORTED: real vocabulary, just not a hard-filter key. Search accounts shows the full rejection body with candidates.

Ordering and pagination

Ordering

sort takes { "by": <field>, "order": "asc" | "desc" }. by is a closed list of stable scalar fields. views and followers_count appear in the accepted examples, and the exact list is an open contract item. A stable resource ID is always the final tie-breaker.

Why query results can’t be sorted

A query-bearing search returns relevance order, and Klint rejects sort sent with a query. A semantic search retrieves a bounded candidate window. Sorting that window by views would present “the most-viewed posts in one retrieval window” as “the most-viewed matching posts on Instagram.” If you need metric-ordered results, define the population structurally (filters-only). Its eligibility is exact, so its ordering is too. An agent can also sort a retrieved page client-side, as long as the answer names that boundary.

Pagination

An initial search pins an immutable result sequence: Klint freezes the request, a snapshot of what it tracks, and the ranking together. The response returns one page plus an opaque cursor:
Continuation sends only the cursor, plus an optional same-or-lower limit:
The rules:
  • The sequence never recomputes. Later pages traverse the pinned set; a post published after your search never appears mid-pagination. Run a new search to see newer state.
  • has_more speaks about the pinned session. has_more: false exhausts the discovered window; it is not proof that Instagram holds no other matches.
  • Pages can run short. Klint suppresses a post deleted after the snapshot and doesn’t refill the page.
  • Cursors expire. After a short lifetime, continuation returns 410 expired-cursor rather than silently recomputing against live data:

Reading the results

Every result, from query search, filters-only, or pagination, is the identical complete post summary. A few semantics make the fields trustworthy:
  • A carousel is one post. media[] holds ordered items in Instagram’s publication order; Klint never splits a carousel into several posts. Transcript, on-screen text, and duration live on the media item that produced them, never flattened onto the post.
  • null, [], and omitted mean different things. null = applicable but not currently supportable (not evaluated yet, source unavailable, or evaluation failed). [] = evaluated and affirmatively empty. An omitted field (video-only fields on an image item) = structurally inapplicable. Failure never degrades to [].
  • Enrichment publishes in whole units. Creative interpretation (content_summary, topics, hook, key_messages, offers, …) appears as one coherent generation; you never see half of it. There is no status or progress field. Re-fetch later; completed units have simply appeared.
  • Metrics are current observations. views, likes, comments follow strict target-post semantics; incompatible source values surface as null, never as guesses. Delivery URLs are Klint-controlled routes, never upstream Instagram CDN URLs.
Follow any result’s id to the full post with Get post; search and GET return the identical representation.

Limits

Provisional bounds (implementation hypotheses; evals may tighten them before contract freeze): Violations use 422 validation-failed with reason OUT_OF_RANGE.

Continue from here

Search accounts

Qualify whole accounts by who they currently are.

API reference

The full request and response schema.