This is the abridged developer documentation for AfterLib API # Overview > AfterLib API documentation for member access to account, credit, ad, page, and collection data. The AfterLib API gives members programmatic access to the same credit-gated data they can use inside the app. Use the API to: * Confirm the connected account name, email address, and credit status. * Search ads and pages. * Fetch a single ad or page by ID. * List collections available to the member. * Read ads or pages from a collection. * Add an ad or page to a collection. API requests use `https://api.afterlib.com/v1`. Start with the [quickstart](/quickstart), then use [examples and recipes](/examples) for plain HTTP clients. All API keys created in the Developer Portal use the same standard access set. ## AI-readable docs [Section titled “AI-readable docs”](#ai-readable-docs) The full docs are also available as [llms.txt](/llms.txt), with [full](/llms-full.txt) and [small](/llms-small.txt) variants for tools that prefer one file. The [Using with LLMs guide](/using-with-llms) shows how to point an assistant at the docs before asking it to help with the API. ## Credit behavior [Section titled “Credit behavior”](#credit-behavior) Reading account identity data, credit status, and the collection list is free. Reading returned ads or pages charges usage credits. Adding an item to a collection does not charge usage credits. Every API response includes a request ID. Keep it when debugging a failed request. # Quickstart > Make the first authenticated AfterLib API request. ## 1. Create a key [Section titled “1. Create a key”](#1-create-a-key) Open the Developer Portal in the AfterLib app, create an API key, and store the full `al_live_...` value somewhere secure. API keys are shown once. If a key is exposed, revoke it and create another one. ## 2. Confirm the connection [Section titled “2. Confirm the connection”](#2-confirm-the-connection) ```bash export AFTERLIB_API_KEY="al_live_replace_me" curl https://api.afterlib.com/v1/whoami \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` If this returns your AfterLib account, the key is working. ```json { "authenticated": true, "authSource": "api_key", "user": { "userId": "00000000-0000-7001-8001-aa0000000001", "billingUserId": "00000000-0000-7001-8001-aa0000000001", "name": "Ada Lovelace", "emailAddress": "ada@example.com", "planId": "pro", "isTeamMember": false, "subscriptionStatus": "active" }, "apiKey": { "id": "019e6387-e2e9-7478-9d6d-170158c523b0", "name": "Claude Desktop", "keyPrefix": "al_live_abc12345" }, "oauthClientId": null } ``` `user.name` and `user.emailAddress` can be `null` when AfterLib cannot resolve them for the connected account. ## 3. Check credits [Section titled “3. Check credits”](#3-check-credits) ```bash curl https://api.afterlib.com/v1/credits \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` ## 4. Search ads [Section titled “4. Search ads”](#4-search-ads) ```bash curl https://api.afterlib.com/v1/ads/search \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search": "running shoes", "limit": 10 }' ``` ## 5. Search websites [Section titled “5. Search websites”](#5-search-websites) Website Library routes are no-credit in this phase: ```bash curl "https://api.afterlib.com/v1/websites/domains?limit=10&search=running" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` ## 6. Work with collections [Section titled “6. Work with collections”](#6-work-with-collections) List collections first: ```bash curl https://api.afterlib.com/v1/collections \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` Then read ads from a collection: ```bash curl "https://api.afterlib.com/v1/collections/00000000-0000-0000-0000-000000000000/items?itemType=ads&limit=25" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` Add an ad to a collection: ```bash curl https://api.afterlib.com/v1/collections/00000000-0000-0000-0000-000000000000/items \ -X POST \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "itemType": "ads", "itemId": "1234567890" }' ``` The [collections guide](/collections) covers page collections, Website Library item types, and idempotent add behavior. ## Next steps [Section titled “Next steps”](#next-steps) * Use [examples and recipes](/examples) for plain curl, JavaScript, TypeScript, PHP, and Python examples. * Use the [websites guide](/websites) for Website Library domain and link routes. * Use [request logs](/request-logs) when a response includes an error or an unexpected credit charge. # Authentication > Authenticate requests with AfterLib API keys. Send your API key as a Bearer token: * curl ```bash curl https://api.afterlib.com/v1/whoami \ -H "Authorization: Bearer al_live_replace_me" ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/whoami', { headers: {Authorization: 'Bearer al_live_replace_me'}, }); const identity = await response.json(); ``` * TypeScript ```ts type WhoamiRequest = Record; const request: WhoamiRequest = {}; const response = await fetch('https://api.afterlib.com/v1/whoami', { headers: {Authorization: 'Bearer al_live_replace_me'}, }); const identity = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/whoami', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer al_live_replace_me', ], ])); $identity = json_decode($response, true); ``` * Python ```py import json import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/whoami', headers={'Authorization': 'Bearer al_live_replace_me'}, ) with urllib.request.urlopen(request) as response: identity = json.loads(response.read().decode('utf-8')) ``` The response includes the connected account id, billing account id, current plan state, display name, and primary email address. ```json { "authenticated": true, "authSource": "api_key", "user": { "userId": "00000000-0000-7001-8001-aa0000000001", "billingUserId": "00000000-0000-7001-8001-aa0000000001", "name": "Ada Lovelace", "emailAddress": "ada@example.com", "planId": "pro", "isTeamMember": false, "subscriptionStatus": "active" }, "apiKey": { "id": "019e6387-e2e9-7478-9d6d-170158c523b0", "name": "Claude Desktop", "keyPrefix": "al_live_abc12345" }, "oauthClientId": null } ``` `user.name` and `user.emailAddress` are nullable. They are returned from the connected AfterLib identity when available. ## Key format [Section titled “Key format”](#key-format) Live keys begin with `al_live_`. Treat them like passwords. Do not send keys in query strings. Query strings are commonly logged by proxies, browsers, and support tools. ## Access [Section titled “Access”](#access) Keys created in the Developer Portal use the standard AfterLib API access set. ## Revocation [Section titled “Revocation”](#revocation) Revoke keys from the Developer Portal. Revoked keys stop working immediately. # Credits and limits > Understand AfterLib API usage credits and limit responses. The API uses the member subscription credits already available in AfterLib. ## Free requests [Section titled “Free requests”](#free-requests) These requests do not charge usage credits: * `GET /v1/whoami` * `GET /v1/account` * `GET /v1/credits` * `GET /v1/collections` * `GET /v1/websites/domains` * `GET /v1/websites/domains/{domainId}` * `GET /v1/websites/domains/{domainId}/pages` * `GET /v1/websites/links` * `GET /v1/websites/links/{linkId}` * `GET /v1/websites/links/{linkId}/pages` * `GET /v1/websites/links/{linkId}/ads` * `POST /v1/collections/{collectionId}/items` Website domain and website link collection operations are no-credit in this phase. ## Charged requests [Section titled “Charged requests”](#charged-requests) Requests that return ad or page records charge credits for the unique items returned: * `POST /v1/ads/search` * `GET /v1/ads/{adId}` * `POST /v1/pages/search` * `GET /v1/pages/{pageId}` * `GET /v1/collections/{collectionId}/items` when it returns ads or pages If a request would exceed the available credits, the API returns an error response instead of partial data. ## Credit status [Section titled “Credit status”](#credit-status) * curl ```bash curl https://api.afterlib.com/v1/credits \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/credits', { headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}, }); const credits = await response.json(); ``` * TypeScript ```ts type CreditsRequest = Record; const request: CreditsRequest = {}; const response = await fetch('https://api.afterlib.com/v1/credits', { headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}, }); const credits = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/credits', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $credits = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/credits', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: credits = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "planId": "pro", "billingUserId": "00000000-0000-7001-8001-aa0000000001", "periodStart": 1779254247, "periodEnd": 1781932647, "credits": 99968, "creditsMax": 100000, "usageLimit": { "isLimitReached": false, "limitType": "none", "hourlyCredits": 0, "hourlyLimit": 5000, "dailyCredits": 32, "dailyLimit": 18000 } } ``` ## Credit headers [Section titled “Credit headers”](#credit-headers) Credit-gated responses include headers that show the remaining credit state when available. Use `GET /v1/credits` for the most readable current status. `GET /v1/credits` returns `usageLimit` as an object when the account has active hourly or daily usage-limit tracking. Free-access accounts can return `usageLimit: null`. # Ads > Search and fetch public ad data. ## Search ads [Section titled “Search ads”](#search-ads) Search ads with optional text, range, date, country, technology, and sorting filters. Returned ad records include `appUrl`, a direct link to open that ad in the AfterLib web app. | Parameter | Location | Type | Default | Allowed values | Description | | ------------------------------------------------ | --------- | ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | | `search` | JSON body | string | none | up to 200 chars | Keyword search across public ad fields. | | `limit` | JSON body | number | `20` | `1` to `100` | Maximum ads returned. | | `cursor` | JSON body | string | none | value from previous response | Fetch the next cursor page. | | `sortBy` | JSON body | string | `performance` | `performance`, `duplicates`, `trend`, `started_at`, `audience_reach`, `duplicates_change_p_all`, `audience_reach_change_p_all` | Sort field. | | `sortDirection` | JSON body | string | `desc` | `asc`, `desc` | Sort direction. | | `textSearch` | JSON body | string or object | none | text string or field include/exclude object | Match text in specific public fields. | | `duplicates`, `performance`, `audienceReach` | JSON body | object | none | `{ "gte": number, "lte": number }` | Numeric range filters. | | `dateRange` | JSON body | object | none | `{ "gte": "YYYY-MM-DD", "lte": "YYYY-MM-DD" }` | Active date range filter. | | `countries`, `technologies`, `publisherPlatform` | JSON body | object | none | `{ "include": [], "exclude": [], "operator": "or" }` | Include/exclude filters. | | `displayFormat`, `ctaType` | JSON body | string array | none | supported public values | Filter by ad format or call to action. | * curl ```bash curl https://api.afterlib.com/v1/ads/search \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search": "coffee", "limit": 10, "sortBy": "performance", "sortDirection": "desc" }' ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/ads/search', { method: 'POST', headers: { Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ search: 'coffee', limit: 10, sortBy: 'performance', sortDirection: 'desc', }), }); const result = await response.json(); ``` * TypeScript ```ts type AdsSearchRequest = { search?: string; limit?: number; cursor?: string; sortBy?: 'performance' | 'duplicates' | 'trend' | 'started_at' | 'audience_reach'; sortDirection?: 'asc' | 'desc'; }; const body: AdsSearchRequest = { search: 'coffee', limit: 10, sortBy: 'performance', sortDirection: 'desc', }; const response = await fetch('https://api.afterlib.com/v1/ads/search', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); const result = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/ads/search', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => [ 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), 'Content-Type: application/json', ], 'content' => json_encode([ 'search' => 'coffee', 'limit' => 10, 'sortBy' => 'performance', 'sortDirection' => 'desc', ]), ], ])); $result = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/ads/search', data=json.dumps({ 'search': 'coffee', 'limit': 10, 'sortBy': 'performance', 'sortDirection': 'desc', }).encode('utf-8'), method='POST', headers={ 'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}", 'Content-Type': 'application/json', }, ) with urllib.request.urlopen(request) as response: result = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "items": [ { "id": "018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1", "appUrl": "https://app.afterlib.com/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1", "headline": "Bring home cleaner skin", "body": "A concise public ad response with creative metadata, media, page, and website context.", "startedAt": "2025-08-26T09:04:49.640Z", "lastSeenAt": "2025-09-03T09:04:49.640Z", "duplicates": 1228, "performancePublic": 100, "trendPublic": 0, "countries": ["US"], "displayFormat": "video", "ctaType": "SHOP_NOW", "media": [ { "type": "THUMB", "url": "creative-thumb.jpg", "mediaFormat": "jpg", "urls": {"preview": "https://box.afterlib.com/example-preview"} } ], "page": {"pageName": "Drift Wellness Company", "adsAmount": 4}, "website": {"siteDomain": "seed-store.afterlib.local"} } ], "cursor": null, "hasMore": false, "totalHits": 1 } ``` ## Search recipe: filtered winners [Section titled “Search recipe: filtered winners”](#search-recipe-filtered-winners) Use include filters and ranges together when you want a narrower set of ads that match a market and threshold. * curl ```bash curl https://api.afterlib.com/v1/ads/search \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search": "coffee", "countries": {"include": ["US"], "operator": "or"}, "technologies": {"include": ["shopify"], "operator": "or"}, "duplicates": {"gte": 100}, "dateRange": {"gte": "2025-01-01"}, "limit": 25 }' ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/ads/search', { method: 'POST', headers: { Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ search: 'coffee', countries: {include: ['US'], operator: 'or'}, technologies: {include: ['shopify'], operator: 'or'}, duplicates: {gte: 100}, dateRange: {gte: '2025-01-01'}, limit: 25, }), }); ``` * TypeScript ```ts type RangeFilter = {gte?: number; lte?: number}; type DateRangeFilter = {gte?: string; lte?: string}; type IncludeExcludeFilter = { include?: string[]; exclude?: string[]; operator?: 'or' | 'and' | 'exact'; }; type AdsSearchRequest = { search?: string; limit?: number; countries?: IncludeExcludeFilter; technologies?: IncludeExcludeFilter; duplicates?: RangeFilter; dateRange?: DateRangeFilter; }; const body: AdsSearchRequest = { search: 'coffee', countries: {include: ['US'], operator: 'or'}, technologies: {include: ['shopify'], operator: 'or'}, duplicates: {gte: 100}, dateRange: {gte: '2025-01-01'}, limit: 25, }; const response = await fetch('https://api.afterlib.com/v1/ads/search', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/ads/search', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => [ 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), 'Content-Type: application/json', ], 'content' => json_encode([ 'search' => 'coffee', 'countries' => ['include' => ['US'], 'operator' => 'or'], 'technologies' => ['include' => ['shopify'], 'operator' => 'or'], 'duplicates' => ['gte' => 100], 'dateRange' => ['gte' => '2025-01-01'], 'limit' => 25, ]), ], ])); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/ads/search', data=json.dumps({ 'search': 'coffee', 'countries': {'include': ['US'], 'operator': 'or'}, 'technologies': {'include': ['shopify'], 'operator': 'or'}, 'duplicates': {'gte': 100}, 'dateRange': {'gte': '2025-01-01'}, 'limit': 25, }).encode('utf-8'), method='POST', headers={ 'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}", 'Content-Type': 'application/json', }, ) ``` ## Fetch one ad [Section titled “Fetch one ad”](#fetch-one-ad) Fetch one ad by its public UUID. | Parameter | Location | Type | Default | Allowed values | Description | | --------- | -------- | ----------- | -------- | -------------- | ---------------- | | `adId` | path | UUID string | required | public ad UUID | The ad to fetch. | * curl ```bash curl https://api.afterlib.com/v1/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1 \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch( 'https://api.afterlib.com/v1/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1', {headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}}, ); const ad = await response.json(); ``` * TypeScript ```ts type GetAdRequest = { adId: string; }; const request: GetAdRequest = { adId: '018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1', }; const response = await fetch('https://api.afterlib.com/v1/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1', { headers: { Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY, }, }); const ad = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $ad = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: ad = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "id": "018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1", "appUrl": "https://app.afterlib.com/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1", "headline": "Bring home cleaner skin", "duplicates": 1228, "performancePublic": 100, "countries": ["US"], "page": {"pageName": "Drift Wellness Company", "adsAmount": 4}, "website": {"siteDomain": "seed-store.afterlib.local"} } ``` `POST /v1/ads/search` and `GET /v1/ads/{adId}` can charge credits for returned unique ads. Save `X-Afterlib-Request-Id` when debugging unexpected results or credit charges. # Pages > Search and fetch public page data. ## Search pages [Section titled “Search pages”](#search-pages) Search pages with optional keyword, range, country, language, technology, and sorting filters. Returned page records include `appUrl`, a direct link to open that page in the AfterLib web app. | Parameter | Location | Type | Default | Allowed values | Description | | -------------------------------------------------- | --------- | ---------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | | `search` | JSON body | string | none | up to 200 chars | Keyword search across public page fields. | | `limit` | JSON body | number | `20` | `1` to `100` | Maximum pages returned. | | `cursor` | JSON body | string | none | value from previous response | Fetch the next cursor page. | | `sortBy` | JSON body | string | `performance` | `performance`, `trend`, `audienceReach`, `pageLikes`, `pageIgFollowers`, `adsTotalDuplicates`, `adsAmount`, `averageDuplicates`, `pageCreatedAt`, `audienceReachChangeP14`, `totalAdsChangeP14`, `uniqueAdsChangeP14` | Sort field. | | `sortDirection` | JSON body | string | `desc` | `asc`, `desc` | Sort direction. | | `textSearch` | JSON body | string or object | none | text string or field include/exclude object | Match text in specific public fields. | | `performance`, `audienceReach`, `pageLikes`, `ads` | JSON body | object | none | `{ "gte": number, "lte": number }` | Numeric range filters. | | `creationDate` | JSON body | object | none | `{ "gte": "YYYY-MM-DD", "lte": "YYYY-MM-DD" }` | Page creation date range. | | `countries`, `languages`, `technologies` | JSON body | object | none | `{ "include": [], "exclude": [], "operator": "or" }` | Include/exclude filters. | * curl ```bash curl https://api.afterlib.com/v1/pages/search \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search": "running", "limit": 10, "sortBy": "performance", "sortDirection": "desc" }' ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/pages/search', { method: 'POST', headers: { Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ search: 'running', limit: 10, sortBy: 'performance', sortDirection: 'desc', }), }); const result = await response.json(); ``` * TypeScript ```ts type PagesSearchRequest = { search?: string; limit?: number; cursor?: string; sortBy?: 'performance' | 'trend' | 'audienceReach' | 'pageLikes' | 'pageIgFollowers'; sortDirection?: 'asc' | 'desc'; }; const body: PagesSearchRequest = { search: 'running', limit: 10, sortBy: 'performance', sortDirection: 'desc', }; const response = await fetch('https://api.afterlib.com/v1/pages/search', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); const result = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/pages/search', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => [ 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), 'Content-Type: application/json', ], 'content' => json_encode([ 'search' => 'running', 'limit' => 10, 'sortBy' => 'performance', 'sortDirection' => 'desc', ]), ], ])); $result = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/pages/search', data=json.dumps({ 'search': 'running', 'limit': 10, 'sortBy': 'performance', 'sortDirection': 'desc', }).encode('utf-8'), method='POST', headers={ 'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}", 'Content-Type': 'application/json', }, ) with urllib.request.urlopen(request) as response: result = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "items": [ { "pageId": "9000000000500", "metaPageExternalId": "9000000000500", "appUrl": "https://app.afterlib.com/pages/9000000000500", "pageName": "Drift Wellness Company", "pageLikes": 2778360, "pageIgFollowers": 992229, "pageCreatedAt": "2021-01-13T09:04:49.640Z", "adsAmount": 4, "adsTotalDuplicates": 1538, "audienceReach": 15436615, "averageDuplicates": 385, "performance": 84, "trend": 1, "countries": ["US", "CA"], "languages": ["eng"], "technologySlugs": ["shopify", "klaviyo"] } ], "cursor": null, "hasMore": false, "totalHits": 1 } ``` ## Search recipe: high-reach pages [Section titled “Search recipe: high-reach pages”](#search-recipe-high-reach-pages) Use page-level filters when you want accounts with enough audience scale and a specific commerce stack. * curl ```bash curl https://api.afterlib.com/v1/pages/search \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search": "running", "countries": {"include": ["US"], "operator": "or"}, "technologies": {"include": ["shopify"], "operator": "or"}, "audienceReach": {"gte": 1000000}, "creationDate": {"gte": "2021-01-01"}, "limit": 25 }' ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/pages/search', { method: 'POST', headers: { Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ search: 'running', countries: {include: ['US'], operator: 'or'}, technologies: {include: ['shopify'], operator: 'or'}, audienceReach: {gte: 1000000}, creationDate: {gte: '2021-01-01'}, limit: 25, }), }); ``` * TypeScript ```ts type RangeFilter = {gte?: number; lte?: number}; type DateRangeFilter = {gte?: string; lte?: string}; type IncludeExcludeFilter = { include?: string[]; exclude?: string[]; operator?: 'or' | 'and' | 'exact'; }; type PagesSearchRequest = { search?: string; limit?: number; countries?: IncludeExcludeFilter; technologies?: IncludeExcludeFilter; audienceReach?: RangeFilter; creationDate?: DateRangeFilter; }; const body: PagesSearchRequest = { search: 'running', countries: {include: ['US'], operator: 'or'}, technologies: {include: ['shopify'], operator: 'or'}, audienceReach: {gte: 1000000}, creationDate: {gte: '2021-01-01'}, limit: 25, }; const response = await fetch('https://api.afterlib.com/v1/pages/search', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/pages/search', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => [ 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), 'Content-Type: application/json', ], 'content' => json_encode([ 'search' => 'running', 'countries' => ['include' => ['US'], 'operator' => 'or'], 'technologies' => ['include' => ['shopify'], 'operator' => 'or'], 'audienceReach' => ['gte' => 1000000], 'creationDate' => ['gte' => '2021-01-01'], 'limit' => 25, ]), ], ])); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/pages/search', data=json.dumps({ 'search': 'running', 'countries': {'include': ['US'], 'operator': 'or'}, 'technologies': {'include': ['shopify'], 'operator': 'or'}, 'audienceReach': {'gte': 1000000}, 'creationDate': {'gte': '2021-01-01'}, 'limit': 25, }).encode('utf-8'), method='POST', headers={ 'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}", 'Content-Type': 'application/json', }, ) ``` ## Fetch one page [Section titled “Fetch one page”](#fetch-one-page) Fetch one page by Meta page ID. | Parameter | Location | Type | Default | Allowed values | Description | | --------- | -------- | ------ | -------- | ----------------------- | ------------------ | | `pageId` | path | string | required | positive page ID string | The page to fetch. | * curl ```bash curl https://api.afterlib.com/v1/pages/9000000000500 \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/pages/9000000000500', { headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}, }); const page = await response.json(); ``` * TypeScript ```ts type GetPageRequest = { pageId: string; }; const request: GetPageRequest = { pageId: '9000000000500', }; const response = await fetch('https://api.afterlib.com/v1/pages/9000000000500', { headers: { Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY, }, }); const page = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/pages/9000000000500', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $page = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/pages/9000000000500', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: page = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "pageId": "9000000000500", "appUrl": "https://app.afterlib.com/pages/9000000000500", "pageName": "Drift Wellness Company", "pageLikes": 2778360, "pageIgFollowers": 992229, "adsAmount": 4, "performance": 84, "countries": ["US", "CA"], "technologySlugs": ["shopify", "klaviyo"] } ``` `POST /v1/pages/search` and `GET /v1/pages/{pageId}` can charge credits for returned unique pages. Save `X-Afterlib-Request-Id` when debugging unexpected results or credit charges. # Websites > Search Website Library domains, links, linked pages, and linked ads. Website Library endpoints expose public-safe domain and landing page summaries through the `/v1` API. These routes return stable metrics, technology slugs, offer and quality fields, timestamps, favicon URLs, counts, linked page previews, and `appUrl` links for opening records in the AfterLib web app. Raw analysis payloads, crawl state, and scrape internals are not included. Website Library routes are no-credit in this phase. ## Search domains [Section titled “Search domains”](#search-domains) | Parameter | Location | Type | Default | Allowed values | Description | | --------------- | -------- | ------ | -------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------- | | `limit` | query | number | `50` | `1` to `100` | Maximum domains returned. | | `offset` | query | number | `0` | `0` or higher | Offset for page-style pagination. | | `search` | query | string | none | up to 255 chars | Match domain text. | | `sortBy` | query | string | `activeAds30d` | `domain`, `activeAds30d`, `activeLinks30d`, `firstSeenAt`, `swGlobalRank`, `lastSeenAt`, `technologySlugs` | Sort field. | | `sortDirection` | query | string | `desc` | `asc`, `desc` | Sort direction. | * curl ```bash curl "https://api.afterlib.com/v1/websites/domains?limit=25&search=example&sortBy=activeAds30d&sortDirection=desc" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const url = new URL('https://api.afterlib.com/v1/websites/domains'); url.search = new URLSearchParams({ limit: '25', search: 'example', sortBy: 'activeAds30d', sortDirection: 'desc', }).toString(); const response = await fetch(url, { headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}, }); const domains = await response.json(); ``` * TypeScript ```ts type WebsiteDomainSearchRequest = { limit?: number; offset?: number; search?: string; sortBy?: 'domain' | 'activeAds30d' | 'activeLinks30d' | 'firstSeenAt' | 'swGlobalRank' | 'lastSeenAt' | 'technologySlugs'; sortDirection?: 'asc' | 'desc'; }; const request: WebsiteDomainSearchRequest = { limit: 25, search: 'example', sortBy: 'activeAds30d', sortDirection: 'desc', }; const url = new URL('https://api.afterlib.com/v1/websites/domains'); url.search = new URLSearchParams( Object.entries(request).map(([key, value]) => [key, String(value)]), ).toString(); const response = await fetch(url, { headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}, }); const domains = await response.json(); ``` * PHP ```php $query = http_build_query([ 'limit' => 25, 'search' => 'example', 'sortBy' => 'activeAds30d', 'sortDirection' => 'desc', ]); $response = file_get_contents("https://api.afterlib.com/v1/websites/domains?$query", false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $domains = json_decode($response, true); ``` * Python ```py import json import os import urllib.parse import urllib.request query = urllib.parse.urlencode({ 'limit': 25, 'search': 'example', 'sortBy': 'activeAds30d', 'sortDirection': 'desc', }) request = urllib.request.Request( f'https://api.afterlib.com/v1/websites/domains?{query}', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: domains = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "items": [ { "id": "019e6387-e2e9-7478-9d6d-170158c523b1", "domain": "seed-store.afterlib.local", "appUrl": "https://app.afterlib.com/websites/domain/019e6387-e2e9-7478-9d6d-170158c523b1", "technologySlugs": ["shopify", "klaviyo"], "categories": ["beauty"], "swGlobalRank": 12450, "swGlobalTraffic": 1200000, "activeAds30d": 42, "activeLinks30d": 8, "activePages30d": 3, "firstSeenAt": "2025-08-26T09:04:49.640Z", "lastSeenAt": "2025-09-03T09:04:49.640Z", "linkedPages": [ { "metaPageExternalId": "9000000000500", "pageName": "Drift Wellness Company", "linkedAdsCount": 12 } ] } ], "pageInfo": {"limit": 50, "offset": 0, "total": 1, "hasMore": false} } ``` ## Search links [Section titled “Search links”](#search-links) | Parameter | Location | Type | Default | Allowed values | Description | | --------------- | -------- | ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- | | `domainId` | query | UUID string | none | Website Library domain ID | Limit links to one domain. | | `limit` | query | number | `50` | `1` to `100` | Maximum links returned. | | `offset` | query | number | `0` | `0` or higher | Offset for page-style pagination. | | `search` | query | string | none | up to 255 chars | Match link or domain text. | | `sortBy` | query | string | `activeAds30d` | `domain`, `linkPath`, `activeAds30d`, `activeAdsChangePercent30d`, `firstSeenAt`, `percentOfDomainAds30d`, `lastSeenAt`, `technologySlugs` | Sort field. | | `sortDirection` | query | string | `desc` | `asc`, `desc` | Sort direction. | * curl ```bash curl "https://api.afterlib.com/v1/websites/links?limit=25&search=product&sortBy=activeAds30d&sortDirection=desc" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const url = new URL('https://api.afterlib.com/v1/websites/links'); url.search = new URLSearchParams({ limit: '25', search: 'product', sortBy: 'activeAds30d', sortDirection: 'desc', }).toString(); const response = await fetch(url, { headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}, }); const links = await response.json(); ``` * TypeScript ```ts type WebsiteLinkSearchRequest = { domainId?: string; limit?: number; offset?: number; search?: string; sortBy?: 'domain' | 'linkPath' | 'activeAds30d' | 'activeAdsChangePercent30d' | 'firstSeenAt' | 'percentOfDomainAds30d' | 'lastSeenAt' | 'technologySlugs'; sortDirection?: 'asc' | 'desc'; }; const request: WebsiteLinkSearchRequest = { limit: 25, search: 'product', sortBy: 'activeAds30d', sortDirection: 'desc', }; const url = new URL('https://api.afterlib.com/v1/websites/links'); url.search = new URLSearchParams( Object.entries(request).map(([key, value]) => [key, String(value)]), ).toString(); const response = await fetch(url, { headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}, }); const links = await response.json(); ``` * PHP ```php $query = http_build_query([ 'limit' => 25, 'search' => 'product', 'sortBy' => 'activeAds30d', 'sortDirection' => 'desc', ]); $response = file_get_contents("https://api.afterlib.com/v1/websites/links?$query", false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $links = json_decode($response, true); ``` * Python ```py import json import os import urllib.parse import urllib.request query = urllib.parse.urlencode({ 'limit': 25, 'search': 'product', 'sortBy': 'activeAds30d', 'sortDirection': 'desc', }) request = urllib.request.Request( f'https://api.afterlib.com/v1/websites/links?{query}', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: links = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "items": [ { "id": "019e6387-e2e9-7478-9d6d-170158c523b2", "domainId": "019e6387-e2e9-7478-9d6d-170158c523b1", "domain": "seed-store.afterlib.local", "appUrl": "https://app.afterlib.com/websites/019e6387-e2e9-7478-9d6d-170158c523b2", "linkPath": "/products/routine", "themeText": "daily routine landing page", "technologySlugs": ["shopify", "klaviyo"], "activeAds30d": 18, "percentOfDomainAds30d": 42.86, "pageType": "product", "hasDiscount": true, "offerType": "percentage", "offerValue": "20%", "pageQuality": "high", "generalSummary": "Public landing page summary for competitive research." } ], "pageInfo": {"limit": 50, "offset": 0, "total": 1, "hasMore": false} } ``` ## Related pages and ads [Section titled “Related pages and ads”](#related-pages-and-ads) Use related routes to move from a domain or landing page to the public pages and ads connected to it. | Parameter | Location | Type | Default | Allowed values | Description | | --------- | -------- | ------ | ------- | --------------- | ---------------------------------------------- | | `limit` | query | number | `50` | `1` to `100` | Maximum related records returned. | | `offset` | query | number | `0` | `0` or higher | Offset for page-style pagination. | | `search` | query | string | none | up to 255 chars | Optional text filter for related pages or ads. | * curl ```bash curl "https://api.afterlib.com/v1/websites/domains/019e6387-e2e9-7478-9d6d-170158c523b1/pages?limit=25&offset=0" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch( 'https://api.afterlib.com/v1/websites/domains/019e6387-e2e9-7478-9d6d-170158c523b1/pages?limit=25&offset=0', {headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}}, ); const pages = await response.json(); ``` * TypeScript ```ts type RelatedDomainPagesRequest = { domainId: string; limit?: number; offset?: number; search?: string; }; const request: RelatedDomainPagesRequest = { domainId: '019e6387-e2e9-7478-9d6d-170158c523b1', limit: 25, offset: 0, }; const params = new URLSearchParams({ limit: String(request.limit), offset: String(request.offset), }); const response = await fetch( 'https://api.afterlib.com/v1/websites/domains/019e6387-e2e9-7478-9d6d-170158c523b1/pages?' + params, {headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}}, ); const pages = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/websites/domains/019e6387-e2e9-7478-9d6d-170158c523b1/pages?limit=25&offset=0', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $pages = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/websites/domains/019e6387-e2e9-7478-9d6d-170158c523b1/pages?limit=25&offset=0', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: pages = json.loads(response.read().decode('utf-8')) ``` Related pages from a landing page: * curl ```bash curl "https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/pages?limit=25" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch( 'https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/pages?limit=25', {headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}}, ); const pages = await response.json(); ``` * TypeScript ```ts type RelatedLinkPagesRequest = { linkId: string; limit?: number; offset?: number; search?: string; }; const request: RelatedLinkPagesRequest = { linkId: '019e6387-e2e9-7478-9d6d-170158c523b2', limit: 25, }; const params = new URLSearchParams({limit: String(request.limit)}); const response = await fetch( 'https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/pages?' + params, {headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}}, ); const pages = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/pages?limit=25', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $pages = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/pages?limit=25', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: pages = json.loads(response.read().decode('utf-8')) ``` Related ads from a landing page: * curl ```bash curl "https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/ads?limit=25" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch( 'https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/ads?limit=25', {headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}}, ); const ads = await response.json(); ``` * TypeScript ```ts type RelatedLinkAdsRequest = { linkId: string; limit?: number; offset?: number; search?: string; }; const request: RelatedLinkAdsRequest = { linkId: '019e6387-e2e9-7478-9d6d-170158c523b2', limit: 25, }; const params = new URLSearchParams({limit: String(request.limit)}); const response = await fetch( 'https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/ads?' + params, {headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}}, ); const ads = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/ads?limit=25', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $ads = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/websites/links/019e6387-e2e9-7478-9d6d-170158c523b2/ads?limit=25', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: ads = json.loads(response.read().decode('utf-8')) ``` These related routes are also no-credit in this phase. # Collections > List collections, read collection items, and add items to a collection. Collections let members work with saved ad, page, website domain, and website link groups through the AfterLib API. ## List collections [Section titled “List collections”](#list-collections) * curl ```bash curl https://api.afterlib.com/v1/collections \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/collections', { headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}, }); const collections = await response.json(); ``` * TypeScript ```ts type ListCollectionsRequest = Record; const request: ListCollectionsRequest = {}; const response = await fetch('https://api.afterlib.com/v1/collections', { headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}, }); const collections = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/collections', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $collections = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/collections', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: collections = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "items": [ { "id": "019e6387-e2e9-7478-9d6d-170158c523b0", "name": "Favorites", "appUrl": "https://app.afterlib.com/collections/019e6387-e2e9-7478-9d6d-170158c523b0", "type": "private", "isDefault": true, "defaultType": "favorites", "isOwner": true, "adCount": 1, "pageCount": 1, "websiteDomainCount": 1, "websiteLinkCount": 1 } ] } ``` The response includes owned collections, accessible team collections, and visible default-style collections such as favorites when available. Hidden collections are not returned. ## Read collection items [Section titled “Read collection items”](#read-collection-items) | Parameter | Location | Type | Default | Allowed values | Description | | -------------- | -------- | ----------- | -------- | -------------------------------------------------- | --------------------------- | | `collectionId` | path | UUID string | required | collection UUID | The collection to read. | | `itemType` | query | string | required | `ads`, `pages`, `website_domains`, `website_links` | The kind of item to return. | | `limit` | query | number | `50` | `1` to `100` | Maximum items returned. | | `cursor` | query | string | none | value from previous response | Fetch the next cursor page. | * curl ```bash curl "https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items?itemType=pages&limit=50" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch( 'https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items?itemType=pages&limit=50', {headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}}, ); const items = await response.json(); ``` * TypeScript ```ts type CollectionItemType = 'ads' | 'pages' | 'website_domains' | 'website_links'; type CollectionItemsRequest = { collectionId: string; itemType: CollectionItemType; limit?: number; cursor?: string; }; const request: CollectionItemsRequest = { collectionId: '019e6387-e2e9-7478-9d6d-170158c523b0', itemType: 'pages', limit: 50, }; const params = new URLSearchParams({ itemType: request.itemType, limit: String(request.limit), }); const response = await fetch( 'https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items?' + params, {headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}}, ); const items = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items?itemType=pages&limit=50', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $items = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items?itemType=pages&limit=50', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: items = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "collection": { "id": "019e6387-e2e9-7478-9d6d-170158c523b0", "name": "Favorites", "appUrl": "https://app.afterlib.com/collections/019e6387-e2e9-7478-9d6d-170158c523b0", "type": "private", "isDefault": true, "defaultType": "favorites", "isOwner": true, "adCount": 1, "pageCount": 1, "websiteDomainCount": 1, "websiteLinkCount": 1 }, "itemType": "pages", "items": [ { "pageId": "9000000000500", "appUrl": "https://app.afterlib.com/pages/9000000000500", "pageName": "Drift Wellness Company", "adsAmount": 4, "performance": 84 } ], "cursor": null, "hasMore": false } ``` Website domain and website link collection reads are no-credit in this phase. Ad and page collection reads can charge viewing credits for returned unique records. ## Add a collection item [Section titled “Add a collection item”](#add-a-collection-item) | Parameter | Location | Type | Default | Allowed values | Description | | -------------- | --------- | ----------- | -------- | ----------------------------------------------------------- | ------------------------- | | `collectionId` | path | UUID string | required | collection UUID | The collection to update. | | `itemType` | JSON body | string | required | `ads`, `pages`, `website_domains`, `website_links` | The item kind. | | `itemId` | JSON body | string | required | ad UUID, page ID, website domain UUID, or website link UUID | The item to save. | * curl ```bash curl https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items \ -X POST \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "itemType": "pages", "itemId": "9000000000500" }' ``` * JavaScript ```js const response = await fetch( 'https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items', { method: 'POST', headers: { Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ itemType: 'pages', itemId: '9000000000500', }), }, ); const result = await response.json(); ``` * TypeScript ```ts type CollectionItemType = 'ads' | 'pages' | 'website_domains' | 'website_links'; type AddCollectionItemRequest = { collectionId: string; body: { itemType: CollectionItemType; itemId: string; }; }; const request: AddCollectionItemRequest = { collectionId: '019e6387-e2e9-7478-9d6d-170158c523b0', body: { itemType: 'pages', itemId: '9000000000500', }, }; const response = await fetch( 'https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify(request.body), }, ); const result = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => [ 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), 'Content-Type: application/json', ], 'content' => json_encode([ 'itemType' => 'pages', 'itemId' => '9000000000500', ]), ], ])); $result = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/collections/019e6387-e2e9-7478-9d6d-170158c523b0/items', data=json.dumps({ 'itemType': 'pages', 'itemId': '9000000000500', }).encode('utf-8'), method='POST', headers={ 'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}", 'Content-Type': 'application/json', }, ) with urllib.request.urlopen(request) as response: result = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "collection": { "id": "019e6387-e2e9-7478-9d6d-170158c523b0", "name": "Favorites", "appUrl": "https://app.afterlib.com/collections/019e6387-e2e9-7478-9d6d-170158c523b0", "type": "private", "isDefault": true, "defaultType": "favorites", "isOwner": true, "adCount": 1, "pageCount": 1, "websiteDomainCount": 1, "websiteLinkCount": 1 }, "itemType": "pages", "itemId": "9000000000500", "added": true } ``` The endpoint is idempotent. If the item already exists in the collection, the response returns `added: false`. Missing or inaccessible collections return `404` so collection existence is not leaked. # Using with LLMs > Give an assistant the right AfterLib API context before it helps with requests or the MCP server. AfterLib publishes AI-readable versions of these docs so assistants can answer API questions from the same public documentation you are reading here. Use this when you want an assistant to draft requests, explain credit behavior, debug response errors, or help connect the MCP server. ## Start with this prompt [Section titled “Start with this prompt”](#start-with-this-prompt) Paste this into your assistant before asking it to write API code or help with the MCP server: ```txt Read https://docs.afterlib.com/llms.txt first. Use the AfterLib API documentation from that file as the source of truth. I want help with AfterLib. Tell me when a request can spend account credits, and for Claude or ChatGPT setup, use the AfterLib MCP server page. ``` For longer planning or debugging sessions, use the full docs file: ```txt Read https://docs.afterlib.com/llms-full.txt first. Use it as the source of truth for AfterLib API behavior, examples, credit usage, request logs, collections, and MCP server setup notes. ``` ## Docs files [Section titled “Docs files”](#docs-files) * [llms.txt](/llms.txt): the recommended starting file for assistants. * [llms-full.txt](/llms-full.txt): the fuller version for longer context windows. * [llms-small.txt](/llms-small.txt): a smaller version for constrained tools. ## MCP server [Section titled “MCP server”](#mcp-server) The official AfterLib MCP server docs live on the [MCP server page](/mcp). For Claude Desktop and ChatGPT desktop, copy the MCP server link from the Developer Portal, add it to the app, sign in to AfterLib, then test the connection by asking: `Who am I in AfterLib?` # Errors > Handle AfterLib API error responses. Errors use a consistent JSON shape: ```json { "error": { "code": "error_code", "message": "Human readable error", "requestId": "request_id" } } ``` ## Common status codes [Section titled “Common status codes”](#common-status-codes) * `401` means the API key is missing, malformed, revoked, or invalid. * `402` means an active subscription or available credits are required. * `403` means the key is valid, but the account cannot use the requested AfterLib API action. * `404` means the resource is missing or inaccessible. * `422` means validation failed after route matching. * `429` means the request cannot be served because usage credits or limits are exhausted. * `500` means the API failed unexpectedly. Every response also includes `X-Afterlib-Request-Id`. Error responses repeat that value as `error.requestId`. Include the request ID when asking support to investigate a request. ## Examples [Section titled “Examples”](#examples) Missing or invalid key: ```json { "error": { "code": "unauthorized", "message": "Missing API key", "requestId": "req_unauthorized" } } ``` Subscription or credits required: ```json { "error": { "code": "credits_exhausted", "message": "Active subscription required", "requestId": "req_payment_required" } } ``` Access unavailable: ```json { "error": { "code": "forbidden", "message": "AfterLib API access is not available for this account", "requestId": "req_forbidden" } } ``` Validation failure: ```json { "error": { "code": "validation_error", "message": "Invalid request", "requestId": "req_validation" } } ``` Usage limit reached: ```json { "error": { "code": "usage_limit_exceeded", "message": "Usage limit reached", "requestId": "req_limited" } } ``` # Request logs > Use request history and request IDs to debug AfterLib API calls. The Developer Portal shows recent AfterLib API requests for the current member account. Request history is useful for: * Confirming which key made a request. * Seeing the endpoint, status, operation, and credit charge. * Debugging validation errors and exhausted credit responses. * Auditing collection item reads and writes. Every response includes `X-Afterlib-Request-Id`. Error responses also include the same value as `error.requestId`. Keep that ID when comparing a client-side failure with the request history in the Developer Portal. AfterLib also records API activity internally so the team can monitor behavior, investigate abuse, and support members. # MCP server > Connect AfterLib to Claude and ChatGPT. AfterLib’s MCP gives you a set of tools that let Claude and ChatGPT search AfterLib, open ads, pages, and Website Library records, and save useful finds to your collections during a normal chat. You do not need to install anything, run commands, or copy a secret key into Claude or ChatGPT. AfterLib runs the MCP server for you. You only need the connection link from the Developer Portal and your normal AfterLib sign-in. ## Connect [Section titled “Connect”](#connect) 1. Open the AfterLib Developer Portal. 2. Copy the MCP server link. 3. Add the link in Claude Desktop or ChatGPT desktop. 4. Sign in to AfterLib when the app asks you to connect. 5. Ask the assistant: `Who am I in AfterLib?` If the assistant can show your AfterLib account, including the connected name or email address when available, the connection is working. After that, ask it to check your credit status before any larger searches. Reads that return ads or pages can use account credits. Website Library tools are no-credit in this phase. Checking credits, listing collections, and saving an item to a collection do not use viewing credits. ## Claude Desktop [Section titled “Claude Desktop”](#claude-desktop) Use Claude Desktop when you want Claude to search AfterLib, inspect ads or pages, and save useful finds to your collections during a chat. 1. Open Claude Desktop. 2. Add a new connector using the MCP server link from the Developer Portal. 3. Sign in to AfterLib in the browser window Claude opens. 4. Return to Claude and ask: `Who am I in AfterLib?` After that first check, ask Claude to check your credit status, then start with a small search before asking for larger research tasks. ## ChatGPT desktop [Section titled “ChatGPT desktop”](#chatgpt-desktop) Use ChatGPT desktop when you want ChatGPT to search AfterLib, inspect ads or pages, and save useful finds to your collections during a chat. 1. Open ChatGPT desktop. 2. Add the MCP server link from the Developer Portal. 3. Sign in to AfterLib when ChatGPT asks you to connect. 4. Ask: `Who am I in AfterLib?` If the account check works, ask ChatGPT to check your credit status. ChatGPT can then use the AfterLib tools available to your account. ## What It Can Do [Section titled “What It Can Do”](#what-it-can-do) * Confirm the connected account. * Check your credit balance. * Search ads. * Open an ad. * Search pages. * Open a page. * Search website domains. * Open a website domain. * Search website links. * Open a website link. * Show pages linked to a website domain or link. * Show ads linked to a website link. * List your collections. * Show ads, pages, website domains, or website links in a collection. * Save an ad, page, website domain, or website link to a collection. Resource results include `appUrl` links for opening ads, pages, website records, and collections in the AfterLib web app. Every request appears in the Developer Portal request log with its status and credit usage. # Changelog > AfterLib API documentation changes. ## 2026-05-28 [Section titled “2026-05-28”](#2026-05-28) * **Non-breaking:** Added nullable `user.name` and `user.emailAddress` fields to `GET /v1/whoami`. * **Docs-only:** Added account identity response examples that show the display name and primary email address. ## Phase 4A Website Library [Section titled “Phase 4A Website Library”](#phase-4a-website-library) * **Non-breaking:** Added Website Library domain, link, linked-page, and linked-ad endpoints to the public `/v1` API. * **Non-breaking:** Added `website_domains` and `website_links` collection item types. * **Non-breaking:** Added hosted MCP Website Library tools. Website Library tools are no-credit in this phase. ## Phase 2 docs package [Section titled “Phase 2 docs package”](#phase-2-docs-package) * Created a standalone `@afterlib/public-api-docs` package for AfterLib API documentation. * Added editorial guides for authentication, credits, ads, pages, collections, pagination, errors, and request logs. * Added docs checks for links, examples, and public-copy safety. ## Change types [Section titled “Change types”](#change-types) AfterLib API changes should be classified before the docs are published: * **Breaking:** removes an endpoint, removes a response field, changes a field type, adds a required request field, changes API key access behavior, changes credit charging behavior, or changes an error code that clients can handle. * **Non-breaking:** adds an optional request field, adds a response field, adds a new endpoint, adds an example, or clarifies wording without changing behavior. * **Deprecation:** keeps behavior working while announcing a future removal or replacement. * **Docs-only:** changes examples, navigation, or explanations without changing API behavior. Breaking changes need a migration note, a changelog entry, and product approval before public launch. Deprecations should include the old behavior, the replacement, and the removal date once that date is known. ## Entry format [Section titled “Entry format”](#entry-format) Use this format for future entries: ```md ## YYYY-MM-DD - **Non-breaking:** Added ... - **Breaking:** Changed ... - **Deprecation:** Deprecated ... - **Docs-only:** Clarified ... ``` # Examples and recipes > Copyable AfterLib API examples without generated clients. These examples use plain HTTP so they work in any environment that can send HTTPS requests. Set the key once: ```bash export AFTERLIB_API_KEY="al_live_replace_me" ``` ## Check account identity [Section titled “Check account identity”](#check-account-identity) Start with `GET /v1/whoami` to confirm which account the key or OAuth connection resolves to. * curl ```bash curl https://api.afterlib.com/v1/whoami \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/whoami', { headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}, }); const identity = await response.json(); ``` * TypeScript ```ts type WhoamiRequest = Record; const request: WhoamiRequest = {}; const response = await fetch('https://api.afterlib.com/v1/whoami', { headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}, }); const identity = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/whoami', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $identity = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/whoami', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: identity = json.loads(response.read().decode('utf-8')) ``` Response: ```json { "authenticated": true, "authSource": "api_key", "user": { "userId": "00000000-0000-7001-8001-aa0000000001", "billingUserId": "00000000-0000-7001-8001-aa0000000001", "name": "Ada Lovelace", "emailAddress": "ada@example.com", "planId": "pro", "isTeamMember": false, "subscriptionStatus": "active" }, "apiKey": { "id": "019e6387-e2e9-7478-9d6d-170158c523b0", "name": "Claude Desktop", "keyPrefix": "al_live_abc12345" }, "oauthClientId": null } ``` ## Search ads with error handling [Section titled “Search ads with error handling”](#search-ads-with-error-handling) Use `fetch`, PHP streams, Python standard library, or curl. The JavaScript, TypeScript, PHP, and Python examples include simple error handling that preserves the public request ID. * curl ```bash curl https://api.afterlib.com/v1/ads/search \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search": "running shoes", "limit": 10 }' ``` * JavaScript ```js const apiKey = process.env.AFTERLIB_API_KEY; if (!apiKey) { throw new Error('AFTERLIB_API_KEY is required'); } const response = await fetch('https://api.afterlib.com/v1/ads/search', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'X-Afterlib-Client': 'example-fetch/1.0', }, body: JSON.stringify({ search: 'running shoes', limit: 10, }), }); const body = await response.json(); if (!response.ok) { console.error(body.error?.requestId, body.error?.code, body.error?.message); throw new Error('AfterLib request failed'); } console.log(body.items); ``` * TypeScript ```ts type AdsSearchRequest = { search?: string; limit?: number; }; const apiKey = process.env.AFTERLIB_API_KEY; if (!apiKey) { throw new Error('AFTERLIB_API_KEY is required'); } const request: AdsSearchRequest = { search: 'running shoes', limit: 10, }; const response = await fetch('https://api.afterlib.com/v1/ads/search', { method: 'POST', headers: { Authorization: 'Bearer ' + apiKey, 'Content-Type': 'application/json', 'X-Afterlib-Client': 'example-fetch/1.0', }, body: JSON.stringify(request), }); const body = await response.json(); if (!response.ok) { console.error(body.error?.requestId, body.error?.code, body.error?.message); throw new Error('AfterLib request failed'); } console.log(body.items); ``` * PHP ```php $apiKey = getenv('AFTERLIB_API_KEY'); $response = file_get_contents('https://api.afterlib.com/v1/ads/search', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', 'X-Afterlib-Client: example-php/1.0', ], 'content' => json_encode([ 'search' => 'running shoes', 'limit' => 10, ]), 'ignore_errors' => true, ], ])); $body = json_decode($response, true); if (isset($body['error'])) { error_log($body['error']['requestId'] . ' ' . $body['error']['code']); throw new RuntimeException('AfterLib request failed'); } ``` * Python ```py import json import os import urllib.error import urllib.request api_key = os.environ["AFTERLIB_API_KEY"] request = urllib.request.Request( "https://api.afterlib.com/v1/ads/search", data=json.dumps({"search": "running shoes", "limit": 10}).encode("utf-8"), method="POST", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "X-Afterlib-Client": "example-python/1.0", }, ) try: with urllib.request.urlopen(request) as response: body = json.loads(response.read().decode("utf-8")) except urllib.error.HTTPError as error: body = json.loads(error.read().decode("utf-8")) response_error = body.get("error", {}) print(response_error.get("requestId"), response_error.get("code"), response_error.get("message")) raise print(body["items"]) ``` ## Check credits before search [Section titled “Check credits before search”](#check-credits-before-search) Use `GET /v1/credits` before a workflow that may return many ads or pages: ```bash curl https://api.afterlib.com/v1/credits \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` If the remaining balance is low, lower `limit`, narrow the search query, or wait for the next billing period. ## Search Website Library without credits [Section titled “Search Website Library without credits”](#search-website-library-without-credits) Website Library reads are no-credit in this phase. * curl ```bash curl "https://api.afterlib.com/v1/websites/links?limit=10&search=product" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` * JavaScript ```js const response = await fetch('https://api.afterlib.com/v1/websites/links?limit=10&search=product', { headers: {Authorization: `Bearer ${process.env.AFTERLIB_API_KEY}`}, }); const links = await response.json(); ``` * TypeScript ```ts type WebsiteLinksRequest = { limit?: number; search?: string; }; const request: WebsiteLinksRequest = { limit: 10, search: 'product', }; const url = new URL('https://api.afterlib.com/v1/websites/links'); url.search = new URLSearchParams( Object.entries(request).map(([key, value]) => [key, String(value)]), ).toString(); const response = await fetch(url, { headers: {Authorization: 'Bearer ' + process.env.AFTERLIB_API_KEY}, }); const links = await response.json(); ``` * PHP ```php $response = file_get_contents('https://api.afterlib.com/v1/websites/links?limit=10&search=product', false, stream_context_create([ 'http' => [ 'header' => 'Authorization: Bearer ' . getenv('AFTERLIB_API_KEY'), ], ])); $links = json_decode($response, true); ``` * Python ```py import json import os import urllib.request request = urllib.request.Request( 'https://api.afterlib.com/v1/websites/links?limit=10&search=product', headers={'Authorization': f"Bearer {os.environ['AFTERLIB_API_KEY']}"}, ) with urllib.request.urlopen(request) as response: links = json.loads(response.read().decode('utf-8')) ``` ## Handle credit and limit errors [Section titled “Handle credit and limit errors”](#handle-credit-and-limit-errors) Treat both `402` and `429` as stop signals for the current workflow. Do not immediately retry the same credit-consuming request. ```ts if (response.status === 402 || response.status === 429) { const body = await response.json(); console.error('AfterLib credits or limits blocked the request', { code: body.error?.code, requestId: body.error?.requestId, }); } ``` `402` means the account needs an active subscription or available credits. `429` means a usage limit has been reached. ## Debug a failed request [Section titled “Debug a failed request”](#debug-a-failed-request) 1. Save `error.requestId` from the error response or the `X-Afterlib-Request-Id` response header. 2. Open the Developer Portal request log. 3. Compare the method, path, status, operation, credit charge, and key prefix. 4. Include the request ID when asking support to investigate. # Pagination > Use cursor pagination with AfterLib API list endpoints. List endpoints return: ```json { "items": [ { "id": "018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1", "appUrl": "https://app.afterlib.com/ads/018f2f8e-8f32-7c11-a4a6-33c73a4d7ad1" } ], "cursor": "next_cursor", "hasMore": true } ``` When `hasMore` is true, pass the returned `cursor` to fetch the next page. For search endpoints, include `cursor` in the JSON request body: ```bash curl https://api.afterlib.com/v1/ads/search \ -H "Authorization: Bearer $AFTERLIB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "limit": 25, "cursor": "next_cursor" }' ``` For collection items, include `cursor` as a query parameter: ```bash curl "https://api.afterlib.com/v1/collections/00000000-0000-0000-0000-000000000000/items?itemType=ads&cursor=25" \ -H "Authorization: Bearer $AFTERLIB_API_KEY" ``` Use `limit` to control page size. Collection item reads default to `50` and allow up to `100`.