Skip to main content
One page with the whole Klint contract. Paste this page’s .md URL into your coding agent, or fetch it directly; it is self-contained.

Which doc to fetch when

Basics

  • Base URL: https://api.tryklint.ai
  • Auth on every request: Authorization: Bearer <api-key> (env var KLINT_API_KEY)
  • JSON bodies in, JSON out; every non-2xx is the same compact JSON problem object
  • IDs are opaque, prefixed, and durable: ig_post_…, ig_account_…, ig_media_…, ig_audio_…. Handles are mutable display identity; never store them as keys.

Choose the operation

Request grammar in one look

Search posts: top-level post block; semantic query and/or filters; author constraints nest under post.author:
Search accounts: top-level account block; identity query and/or filters; publishing constraints nest under account.posts:
Rules that prevent almost every 422:
  1. Every filter value is an operator object ({"in": [...]}, {"contains_any": [...]}, {"contains_all": [...]}, {"gte": ..., "lte": ...}) or a direct boolean. Never a bare value.
  2. Singular fields (format, account_type, primary_language, id, handle, audio.id) take in. Collection fields (hashtags, caption_mentions, topics, niches, creative_formats, hook.devices, hook.modalities, commercial_contexts, publishing_languages, declared_locations) take exactly one of contains_any / contains_all. Scalars (posted_at, views, likes, comments, followers_count, engagement_rate_by_views, view_ratio_to_account_baseline) take ranges. is_verified / has_paid_partnership_label take direct booleans.
  3. Fields AND together. There is no negation, no OR, no exists; the grammar is positive-only.
  4. sort ({"by": ..., "order": "asc"|"desc"}) only on filters-only requests; a query owns its ordering and rejects sort.
  5. In account.posts.filters, the complete predicate must hold on one single post; minimum_matching_posts (1–3) counts distinct posts that each satisfy all of it.
  6. Hashtags without #, handles without @ (Klint tolerates and strips leading symbols). Classification values are exact catalog IDs; Klint rejects unknown IDs with candidates, never silently empty.
  7. Limits (provisional): 100 values per membership array, 20 filter fields per request, 2,000-char query, limit ≤ 100.

Responses

  • Search envelope: { "request_id", "data": [...], "has_more", "next_cursor" }. GETs return the bare resource.
  • data items are complete fixed-shape resources: post summaries or accounts. null = can’t currently support the value; [] = affirmatively empty. Never treat null as 0 or false.
  • Continue a result set by POSTing { "cursor": "...", "limit": <same-or-lower> } to the same route, nothing else in the body. Cursors expire (~15–30 min) → 410 expired-cursor → run a new search.
  • An empty data means no qualifying match in what Klint has observed, never proof the content doesn’t exist on Instagram. Same for 404 not-tracked on GETs.
  • Start with limit: 10; paginate only when the task actually consumes more.
  • Put hard constraints (dates, counts, formats, languages, locations) in filters; keep the query for meaning. Describe content, not keywords: “UGC-style grooming-product demonstrations”, not “dog grooming viral”.
  • For discovery-then-qualification workflows: posts search first, collect distinct author.ids, then account GETs, not the other way around.
  • Full posts (GET /posts/{id}) are large; fetch them only when you need media, transcript, or on-screen text. The search summary already carries interpretation and metrics.

Errors

Common mistakes

Availability caveat

Exact limits, bounds, and open items marked across these docs may change before launch; this page reflects the contract as of its last update. When something here disagrees with the API reference, the reference wins.