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.
- curl
- Python
- TypeScript
Full response
Full 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
nullwhere Klint can’t currently support a value. See reading the results.
Writing a query that qualifies
Writepost.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.
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_anyandcontains_allnever 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
trueorfalse, 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_languagefor"en"matchesen,en-US, anden-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"todog_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
queryfor 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 nestedpost.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.
Filters-only search
Skipquery to define a structural population. Add sort to order it:
Full response
Full response
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 returnsdata: []. An invalid filter never executes; it returns a 422 validation-failed problem object that names the exact pointer, reason, and correction.
- Non-matching (valid, empty)
- Invalid (rejected, 422)
Valid requests, empty or reduced results, no error:
"hashtags": { "contains_all": ["groomingtips", "deshedding"] }against a post carrying onlygroomingtips:contains_allneeds every value."has_paid_partnership_label": falseagainst a post whose label state isnull: unknown is not false."views": { "gte": 100000 }against a carousel whoseviewsisnull: 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 hasdevices: []: an empty classification can’t satisfy a device filter (the post stays findable by semantic query).
sort on a query-bearing request: a query owns its ordering; sort is only for filters-only requests:
shares) don’t exist here:
not_in, exclude, or none:
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 rejectssort 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:Page 1 response
Page 1 response
limit:
Final page response
Final page response
- 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_morespeaks about the pinned session.has_more: falseexhausts 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-cursorrather 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 nostatusor progress field. Re-fetch later; completed units have simply appeared. - Metrics are current observations.
views,likes,commentsfollow strict target-post semantics; incompatible source values surface asnull, never as guesses. Delivery URLs are Klint-controlled routes, never upstream Instagram CDN URLs.
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.