Tenor → GIPHY-compatible layer
The simplest way to move away from Tenor is to target the Heypster layer we built on the GIPHY model. In practice, you are not replacing Tenor with a Tenor-compatible API, but with a Heypster API exposed under `/giphy` using GIPHY conventions.
The main diff is concentrated around three topics: `key` becomes `api_key`, cursor pagination `pos/next` becomes numeric `offset/limit`, and parsing moves from `results/media_formats` to `data/images`.
Authentication aligned with GIPHY
Pagination to recalculate
New response schema
Before / after
// before (Tenor)
const API_HOST = 'https://tenor.googleapis.com';
fetch(`${API_HOST}/v2/search?q=party&key=YOUR_TENOR_KEY`);
// after (Heypster)
const API_HOST = 'https://heypster-gif.com/giphy';
fetch(`${API_HOST}/v1/gifs/search?q=party&api_key=YOUR_HEYPSTER_API_KEY`);
When coming from Tenor, the first change is replacing the host `https://tenor.googleapis.com` with `https://heypster-gif.com/giphy`. The second is replacing the `key` parameter with `api_key`, in line with the GIPHY-compatible layer exposed by Heypster.
# Tenor
curl "https://tenor.googleapis.com/v2/search?q=excited&key=YOUR_TENOR_KEY"
# Heypster
curl "https://heypster-gif.com/giphy/v1/gifs/search?q=excited&api_key=YOUR_HEYPSTER_API_KEY"
| Tenor | Heypster | Notes |
|---|---|---|
https://tenor.googleapis.com/v2/search |
https://heypster-gif.com/giphy/v1/gifs/search |
Primary keyword search. |
https://tenor.googleapis.com/v2/featured |
https://heypster-gif.com/giphy/v1/gifs/trending |
Discovery / trending feed. |
https://tenor.googleapis.com/v2/categories |
https://heypster-gif.com/giphy/v1/gifs/categories |
Categories exposed by the GIPHY-compatible layer. |
https://tenor.googleapis.com/v2/autocomplete |
https://heypster-gif.com/giphy/v1/gifs/search/tags |
Query suggestions. |
https://tenor.googleapis.com/v2/search_suggestions |
https://heypster-gif.com/giphy/v1/tags/related/{term} |
Suggestions related to a specific term. |
https://tenor.googleapis.com/v2/trending_terms |
https://heypster-gif.com/giphy/v1/trending/searches |
Trending search terms. |
https://tenor.googleapis.com/v2/search?random=true |
https://heypster-gif.com/giphy/v1/gifs/random |
Heypster returns a single item, not a random list. |
https://tenor.googleapis.com/v2/posts?ids=<ids> |
https://heypster-gif.com/giphy/v1/gifs?ids=<ids> |
Comma-separated ID list. |
https://tenor.googleapis.com/v2/anonid |
https://heypster-gif.com/giphy/v1/randomid |
Random identifier on the GIPHY-compatible API side. |
| Tenor | Heypster | Description |
|---|---|---|
key |
api_key |
The parameter name changes. |
q |
q |
The search term stays the same. |
pos |
offset |
Tenor uses a token; Heypster follows GIPHY with numeric pagination. |
contentfilter |
rating |
Content filtering follows the GIPHY vocabulary. |
anon_id / anonid |
/giphy/v1/randomid |
Use the `randomid` workflow if your product needs a consistent anonymous identifier. |
media_formats |
images |
Format mapping goes through the `images` object. |
Tenor uses `pos` and returns a `next` token. When migrating to Heypster through our GIPHY-compatible layer, you move to classic numeric pagination: `limit` sets the page size and `offset` tells how many items to skip.
// Tenor
const response1 = await fetch('https://tenor.googleapis.com/v2/search?q=cats&key=KEY');
const data1 = await response1.json();
const response2 = await fetch(`https://tenor.googleapis.com/v2/search?q=cats&key=KEY&pos=${data1.next}`);
// Heypster
const response1 = await fetch('https://heypster-gif.com/giphy/v1/gifs/search?q=cats&api_key=KEY&limit=25&offset=0');
const data1 = await response1.json();
const response2 = await fetch('https://heypster-gif.com/giphy/v1/gifs/search?q=cats&api_key=KEY&limit=25&offset=25');
A Tenor parser usually reads `results` and `next`. Here you need to read `data`, `pagination`, and `meta`. This is the main structural change after pagination.
{
"data": [],
"pagination": {
"total_count": 500,
"count": 25,
"offset": 0
},
"meta": {
"status": 200,
"msg": "OK",
"response_id": "heypster-response-id"
}
}
const payload = await response.json();
const items = payload.data;
const offset = payload.pagination?.offset ?? 0;
const count = payload.pagination?.count ?? items.length;
const nextOffset = offset + count;
const status = payload.meta?.status;
| Tenor | Heypster | Description |
|---|---|---|
results[] |
data[] |
The main array changes name. |
next |
pagination.offset + pagination.count |
No opaque cursor anymore: the next page is calculated. |
results[].media_formats |
data[].images |
Asset variants are read from `images`. |
results[].content_rating |
data[].rating |
Content rating is read from `rating`. |
results[].title / content_description |
data[].title |
Start by reading `title` as the main label. |
| Tenor rendition | Heypster rendition | Usage |
|---|---|---|
gif |
images.original.url |
Primary GIF version. |
tinygif |
images.fixed_width.url or images.fixed_height.url |
Lightweight version for picker or grid. |
mediumgif |
images.downsized.url |
Optimized version for standard display. |
preview |
images.preview_gif.url |
Fast preview. |
If your Tenor integration used `/v2/registershare`, plan a dedicated adaptation: the Heypster surface documented here focuses on search, discovery, and identification endpoints (`randomid`), not on a Tenor-style share-tracking endpoint.
const API_HOST = 'https://heypster-gif.com/giphy';
const API_KEY = 'YOUR_HEYPSTER_API_KEY';
export async function searchTenorLike(query, page = 0, limit = 25) {
const offset = page * limit;
const url = new URL(`${API_HOST}/v1/gifs/search`);
url.searchParams.set('api_key', API_KEY);
url.searchParams.set('q', query);
url.searchParams.set('limit', String(limit));
url.searchParams.set('offset', String(offset));
url.searchParams.set('rating', 'g');
const response = await fetch(url.toString());
const payload = await response.json();
return {
items: payload.data,
pagination: payload.pagination,
nextOffset: (payload.pagination?.offset ?? 0) + (payload.pagination?.count ?? 0),
meta: payload.meta,
};
}