Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: HonestHook Instagram API
version: 1.0.0
description: Profiles and posts from the major social networks, through one API. One key, one response shape; rate limits, retries and platform changes are on us. Clean JSON for your app or your AI agent. We read the exact field each platform publishes, and return null when none exists. Plus a historical trend archive that answers what was trending, which cannot be reconstructed after the fact.
contact:
url: https://honesthook.com
license:
name: Proprietary
url: https://honesthook.com/docs
servers:
- url: https://honesthook.com
security:
- bearerAuth: []
tags:
- name: Instagram
paths:
/api/v1/instagram/profile:
get:
operationId: instagramProfile
summary: Public profile on instagram, with the exact follower count
x-credits: 1
x-charges-without-result: false
description: 'One public profile, normalised into the same envelope as every other platform. Source: public profile page, logged out. Costs 1 credit; the answer is cached for an hour and a cache hit costs nothing (`cached: true`, `credits_used: 0`).
Instagram username: letters, digits, dot and underscore, up to 30.'
parameters:
- name: handle
in: query
required: true
description: 'Instagram username: letters, digits, dot and underscore, up to 30.'
schema:
type: string
pattern: ^[A-Za-z0-9._]{1,30}$
example: natgeo
responses:
'200':
description: Profile found
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileEnvelope'
'400':
description: '`parametro_invalido` — the handle is missing or does not match this platform''s format. Nothing is charged.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileEnvelope'
'401':
description: '`chave_invalida` — key missing, unknown or inactive.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileEnvelope'
'402':
description: 'Out of credits. Two different types, and they mean different things: `sem_creditos` (both wallets are empty) and `teto_estourado` (the key has a monthly ceiling and hit it). Waiting does not help either one before the 1st — this is not a rate limit.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileEnvelope'
'404':
description: '`nao_encontrado` — no such profile on that platform.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileEnvelope'
'451':
description: '`bloqueado` — the platform refused us. The resource exists and YOUR key is fine; 403 would wrongly blame the caller.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileEnvelope'
'503':
description: '`indisponivel` — this endpoint is switched off in the catalogue, or a dependency of ours is down. Your key is fine.'
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileEnvelope'
tags:
- Instagram
/api/v1/instagram/highlights:
get:
operationId: instagramHighlights
summary: Story highlight covers of a public Instagram profile — id, title and cover
description: 'The story highlight tray of a public Instagram profile: id, title and cover image for each highlight, read from the SAME logged-out profile page as instagram/profile. Measured on 2026-10-01 against the committed 829 KB page for nasa: 5 highlights, and has_next_page false -- so that is the whole tray, and has_more says so. Does NOT return the media inside a highlight: opening one needs a logged-in session, so no logged-out surface carries it. No date and no counter per highlight -- the tray has neither, so both are null. next_cursor is null even though the page carries an end_cursor: no parameter here accepts one, and paging the tray would mean calling Instagram GraphQL, another surface. position is null on purpose -- Instagram orders the tray by recent update, not the owner. Costs 1 credit and caches for an hour; the cache is per endpoint and params, so asking for profile and highlights on the same handle downloads the page twice.'
x-creditos: 1
parameters:
- name: handle
in: query
required: true
schema:
type: string
responses:
'200':
description: Data found.
'400':
description: See the shared error shape.
'401':
description: See the shared error shape.
'402':
description: See the shared error shape.
'404':
description: See the shared error shape.
'405':
description: See the shared error shape.
'451':
description: See the shared error shape.
'502':
description: See the shared error shape.
'503':
description: See the shared error shape.
tags:
- Instagram
/api/v1/instagram/embed:
get:
operationId: instagramEmbed
summary: 'What an Instagram embed shows: the profile plus the thumbnails on its grid'
description: 'What an Instagram embed shows: the profile plus the posts that come on the public page itself -- caption, thumbnail, media type (image, video, carousel) and the post URL. NOT an alias of instagram/profile: it returns the list that route returns empty. NOT named posts either, because the */posts family here returns per-post counters and this page has none -- like_count, comment_count and play_count appear ZERO times in the 829 KB page, so metrics is null and never an empty object. published_at is null: the only date in the document is day-precision text inside localized alt text, sitting next to a third party name, and a guessed hour would be worse than nothing. Measured on 2026-10-01 for nasa: the grid carries 12 nodes and 7 belong to nasa -- the other 5 are collaborators (nasajohnson, astro_anil, jeopardy) and are dropped by user.username, the same filter threads/posts uses. has_more comes from the page own has_next_page and is true; there is no cursor to hand out. Costs 1 credit and caches for an hour; the cache is per endpoint and params, so the three Instagram routes on the same handle download the page three times.'
x-creditos: 1
parameters:
- name: handle
in: query
required: true
schema:
type: string
responses:
'200':
description: Data found.
'400':
description: See the shared error shape.
'401':
description: See the shared error shape.
'402':
description: See the shared error shape.
'404':
description: See the shared error shape.
'405':
description: See the shared error shape.
'451':
description: See the shared error shape.
'502':
description: See the shared error shape.
'503':
description: See the shared error shape.
tags:
- Instagram
/api/v1/instagram/location/posts:
get:
operationId: instagramLocationPosts
summary: Recent posts tagged at a public Instagram location, plus the location itself
description: 'Recent posts tagged at a public Instagram location, plus the location itself (name, slug, category and how many posts are tagged there). Order is what the platform served, NOT chronological. Views, reposts and quotes do not exist here. Anchored on the location id: a location that does not exist answers 404, never an empty list.'
x-creditos: 1
parameters:
- name: location_id
in: query
required: true
schema:
type: string
responses:
'200':
description: Data found.
'400':
description: See the shared error shape.
'401':
description: See the shared error shape.
'402':
description: See the shared error shape.
'404':
description: See the shared error shape.
'405':
description: See the shared error shape.
'451':
description: See the shared error shape.
'502':
description: See the shared error shape.
'503':
description: See the shared error shape.
tags:
- Instagram
/api/v1/instagram/highlight/detail:
get:
operationId: instagramHighlightDetail
summary: The stories inside one public Instagram highlight, plus the highlight and its…
description: 'The stories inside one public Instagram highlight, oldest first (measured: chronological, unlike the location tab). Returns the highlight itself -- id, title, cover, how many stories -- plus the owner handle and whether that account is verified or private. Story view counts do not exist here: they are owner metrics and need a logged-in session. Anchored on the highlight id echoed back by the page, so a highlight that does not exist answers 404, never an empty list.'
x-creditos: 1
parameters:
- name: id
in: query
required: true
schema:
type: string
responses:
'200':
description: Data found.
'400':
description: See the shared error shape.
'401':
description: See the shared error shape.
'402':
description: See the shared error shape.
'404':
description: See the shared error shape.
'405':
description: See the shared error shape.
'451':
description: See the shared error shape.
'502':
description: See the shared error shape.
'503':
description: See the shared error shape.
tags:
- Instagram
/api/v1/instagram/post:
get:
operationId: instagramPost
summary: A single public Instagram post, by its shortcode
description: 'One public Instagram post by SHORTCODE, from instagram.com/p/{code}/ -- the logged-out page, no account of ours and none of a third party. THE PARSER ALREADY EXISTED WITH NO DOOR: normalizarPost has been in lib/normalize/instagram.js with a committed 934 KB fixture, and no route used it; this is the door, same as bluesky/post. Returns id, url, caption text, thumbnail_url, published_at and metrics with likes and comments -- measured on shortcode Db9IVmrDvQ4: 2445668 likes, 4703 comments. THE AUTHOR COMES BACK ALL NULL, and that is us not filling it in, NOT the platform lacking it: the document carries the owner (user appears 30 times), and normalizarPost does not extract it today -- saying the author is unknown would be a plausible value in the wrong place. title is null because an Instagram post has a caption, not a title, and inventing one from the first line of the caption would serve a guess as a field. A shortcode that does not exist is not reliably distinguishable from our own transport failing, which is why no result is never charged here.'
x-creditos: 1
parameters:
- name: code
in: query
required: true
schema:
type: string
responses:
'200':
description: Data found.
'400':
description: See the shared error shape.
'401':
description: See the shared error shape.
'402':
description: See the shared error shape.
'404':
description: See the shared error shape.
'405':
description: See the shared error shape.
'451':
description: See the shared error shape.
'502':
description: See the shared error shape.
'503':
description: See the shared error shape.
tags:
- Instagram
components:
schemas:
ProfileEnvelope:
type: object
required:
- success
- platform
- endpoint
- data
- error
- credits_used
- credits_remaining
- cached
- request_id
description: Identical shape on success and on error, so a client writes one parser. `success` is the only field that decides; `data` and `error` are mutually exclusive and the other is null, never absent.
properties:
success:
type: boolean
platform:
type: string
endpoint:
type: string
data:
type:
- object
- 'null'
properties:
author:
$ref: '#/components/schemas/Author'
items:
type: array
items:
type: object
has_more:
type:
- boolean
- 'null'
next_cursor:
type:
- string
- 'null'
error:
type:
- object
- 'null'
properties:
type:
type: string
enum:
- parametro_invalido
- chave_invalida
- sem_creditos
- teto_estourado
- nao_encontrado
- bloqueado
- indisponivel
description: Branch on this, never on the message text.
message:
type: string
details:
type:
- object
- 'null'
credits_used:
type: integer
credits_remaining:
type:
- integer
- 'null'
description: 'THE SUM: purchased balance plus what is left of this month''s free allowance. This is the only number that answers "can I call again" — it is not 0 while you can still call. null means we could not read it, which is not the same as zero.'
credits_purchased_remaining:
type:
- integer
- 'null'
description: Bought credits. These never expire.
free_credits_remaining:
type:
- integer
- 'null'
description: What is left of this month's free allowance.
free_credits_total:
type:
- integer
- 'null'
description: The monthly free allowance for this key.
free_credits_reset_at:
type: string
format: date-time
description: 'When the free allowance resets: 00:00 UTC on the 1st. It is here so the sum dropping at the turn of the month is expected rather than a surprise.'
cached:
type: boolean
description: '`true` is a promise that we did NOT charge: it always comes with `credits_used: 0`.'
request_id:
type: string
Author:
type: object
description: 'The same twelve names on every platform. A field that does not exist there is null, never absent and never zero: zero is a fact about the account, null is a fact about our reading.'
properties:
id:
type:
- string
- 'null'
description: Stable identifier on that platform.
handle:
type:
- string
- 'null'
name:
type:
- string
- 'null'
bio:
type:
- string
- 'null'
avatar_url:
type:
- string
- 'null'
followers:
type:
- integer
- 'null'
following:
type:
- integer
- 'null'
posts_count:
type:
- integer
- 'null'
verified:
type:
- boolean
- 'null'
is_private:
type:
- boolean
- 'null'
external_url:
type:
- string
- 'null'
url:
type:
- string
- 'null'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'API key, sent as `Authorization: Bearer hk_live_...`. Quota is monthly and renews on the 1st. Unused quota does not roll over and does not expire mid-cycle — you get the whole month.'
x-provenance:
method: harvested
publisher: HonestHook
source: https://honesthook.com/openapi.json
note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
stamped by stamp-openapi-provenance.py (roadmap#793)