Testing your integration
Test against the live API without surprises. What is free, what is billed, and which paths to check before launch.
Begin with a call that costs nothing. It proves your key works and records your starting balance before any billed test.
curl --include "https://api.insightsocial.app/v1/credits" \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY"{
"success": true,
"credits_used": 0,
"credits_remaining": 500,
"plan": { "tier": "free", "credits_per_month": 500, "resets_at": "2026-10-01T00:00:00.000Z" },
"usage": { "window_start": "2026-09-01T00:00:00.000Z", "used": 0, "used_export": 0, "used_api": 0 },
"pack_credits": 0,
"free_calls": { "remaining": 10, "total": 10 },
"key": { "id": "3f6c2a8e-7b41-4d0f-9a52-1c8e5d7b9f20", "name": "staging" },
"request_id": "req_1a2b3c4d5e6f"
}Check for status 200, success: true and credits_used: 0. Keep the request_id in your test log.
There is no sandbox
Every test runs against the real API, with the same prices, caching and rate limits as production.
Keys come in two forms, isk_live_… and isk_test_…. They behave identically: an isk_test_ key reaches the same data and spends real credits from the same balance. The prefix is a label for your own bookkeeping, not a test mode.
Use fixtures for parser tests that must be deterministic, and a small number of budgeted live calls to prove the end-to-end path works today. Neither proves that a source will be available tomorrow.
Use a separate test key
Create a key just for development or staging at Dashboard → API keys and keep it apart from production. Store it as INSIGHTSOCIAL_API_KEY in a server-side secret. Keep it out of source control, browser code, snapshots and logs.
A separate key lets you revoke it without touching production, and it has its own rate limits. It does not have its own budget: keys cannot be capped, and every key spends from the account balance. Record the balance before and after each test run, and revoke the key if it ever shows up in test output. See Authentication.
Free checks first
These cost nothing:
| Check | What it proves |
|---|---|
GET /v1/endpoints | The catalogue, prices and parameters. Needs no key, so it does not test yours. |
GET /v1/credits | Your key is valid. Returns balance, plan and free calls left. |
dry_run=1 on endpoints that support it | Returns a cost estimate in data.estimate without running the call. |
| Any call that returns an error | Errors are never charged. |
Then make one real call to check auth, transport and your parser against the live service:
curl --include "https://api.insightsocial.app/v1/instagram/profile?handle=natgeo" \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY"This endpoint costs 20 credits. On a new account it may use one of your 10 lifetime free calls instead (free_call: true, credits_used: 0). Read credits_used and charge_reason rather than assuming what was billed, and keep live calls out of unit tests and pull-request loops.
Try calls in the Explorer
The Explorer in your dashboard runs any endpoint from the browser, signed in as you, with no key to paste. Use it to see real responses and prices before you write code. Its calls are real and are charged exactly like API calls.
Test fixtures, nulls and dropped rows
Keep small response fixtures in your own test suite, with keys and unneeded personal data removed. This synthetic one covers the states a parser must tell apart:
{
"success": true,
"platform": "tiktok",
"endpoint": "/v1/tiktok/profile/videos",
"data": {
"items": [
{
"post": {
"id": "7418806235590471732",
"url": "https://www.tiktok.com/@khaby.lame/video/7418806235590471732",
"content": { "text": "Launch day", "media_urls": null, "thumbnail_url": null },
"author": { "username": "khaby.lame", "display_name": null, "verified": true },
"engagement": { "views": 1200, "likes": 100, "comments": 12, "shares": null },
"published_at": "2026-09-01T12:00:00.000Z"
},
"computed": { "engagement_rate": null, "language": null }
}
],
"dropped": 1,
"_warnings": ["One source row could not be normalized."]
},
"pagination": { "next_cursor": null, "has_more": false, "page_size": 1 },
"credits_used": 20,
"credits_remaining": 480,
"request_id": "req_0f1e2d3c4b5a",
"cached": false,
"idempotent_replay": false,
"charge_reason": "miss",
"free_call": false
}Assert each case on its own:
- A present value, such as
post.author.username. - A
null, such ascomputed.language: the field exists but has no value. - An absent optional field. This fixture has no
post.ext, so check that a key exists before reading it. data.dropped: source rows that could not be normalized on this page. Record it per page.data._warnings: advisory notes. The response is still valid.
See Response schema for the full envelope.
For lists, test a one-page and a multi-page walk: send pagination.next_cursor back unchanged as cursor and stop on has_more: false. See Pagination.
Keep error fixtures too, and branch on error.type:
{
"success": false,
"error": {
"type": "RATE_LIMITED",
"message": "Too many requests on this key. Honour Retry-After, then back off with jitter."
},
"request_id": "req_9a8b7c6d5e4f",
"credits_used": 0,
"credits_remaining": 480
}Add one for every error type you handle: at least INVALID_API_KEY, INSUFFICIENT_CREDITS, RATE_LIMITED, CONCURRENCY_LIMIT and SERVICE_UNAVAILABLE. See Error handling and the retry wrapper in the Production checklist.
Test repeat calls and forced fetches
Send the same call twice:
curl "https://api.insightsocial.app/v1/instagram/profile?handle=natgeo" \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY"
curl "https://api.insightsocial.app/v1/instagram/profile?handle=natgeo" \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY"The first call may come back as a miss at the full price, or as shared_cache (cached: true) at 5 credits. Either way it counts as paid (so does a call covered by a free call), and the identical second call inside the endpoint's window should come back with charge_reason: "owned" and credits_used: 0. Assert on the charge_reason and credits_used you actually receive. See Caching.
Test a forced fetch only when you need one, and budget it at the endpoint's full price:
curl "https://api.insightsocial.app/v1/instagram/profile?handle=natgeo" \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY" \
-H "Cache-Control: no-cache"A successful forced fetch is never owned: assert charge_reason: "miss" and the normal charge.
Test retries with an Idempotency-Key
Send the same call twice with one key:
IDEMPOTENCY_KEY="$(uuidgen)"
curl "https://api.insightsocial.app/v1/instagram/profile?handle=nasa" \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY"
curl "https://api.insightsocial.app/v1/instagram/profile?handle=nasa" \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY"The second response should have idempotent_replay: true, charge_reason: "replay" and credits_used: 0. If the second request arrives while the first is still running, expect 409 IDEMPOTENCY_IN_PROGRESS; wait for Retry-After and send it again. Use a new key for every distinct call. See Production checklist.
Pre-production checklist
- The test key is separate from production, and the balance is recorded before and after each run.
- The suite lists each endpoint, its current price, and the most calls it will make.
- Free checks (
/v1/credits,/v1/endpoints,dry_run=1) run before any billed call. - Success and error fixtures cover values,
null, absent fields, dropped rows and warnings. - Pagination tests stop on
has_more: falseand return the cursor unchanged. - Retry tests branch on
error.type, use bounded delays, and resend the same cursor. - Repeat-call tests check
charge_reason,cachedandcredits_used. - Forced fetches are budgeted as billed calls.
- Idempotency tests replay one identical call and use a new key for anything else.
- Logs tie together status,
request_id,error.type, charge reason, credits and attempt count. - Production keys never enter the test environment or its output.