This guide provides comprehensive information for developers migrating from the Tenor API to the GIPHY API. While both APIs provide access to GIF and sticker content, there are important differences in endpoints, parameters, response structures, and features.
Want a faster, easier integration? Check out our official GIPHY SDKs for JavaScript, iOS, and Android. The SDK handles pagination, analytics tracking, and rendition optimization automatically, letting you focus on building great experiences instead of managing API details.
API keys from Tenor will not work with GIPHY.
You must create a new API key in the GIPHY Developer Dashboard.
Plan for testing time as pagination and analytics tracking work differently.
Authentication differs between Tenor and GIPHY:
api_key query parameter instead of Tenor's key parametercurl "https://tenor.googleapis.com/v2/search?q=excited&key=YOUR_TENOR_KEY"curl "https://api.giphy.com/v1/gifs/search?q=excited&api_key=YOUR_GIPHY_KEY"| Platform | Base URL |
|---|---|
| Tenor | https://tenor.googleapis.com |
| GIPHY | https://api.giphy.com |
Below is a comprehensive mapping of Tenor endpoints to their GIPHY equivalents:
| Tenor | GIPHY | Notes |
|---|---|---|
/v2/search | /v1/gifs/search | See pagination differences below |
| Tenor | GIPHY | Notes |
|---|---|---|
/v2/search?searchfilter=sticker | /v1/stickers/search | Uses URL slug instead of query parameter |
remove_low_contrast=true| Tenor | GIPHY |
|---|---|
/v2/featured | /v1/gifs/trending |
/v2/featured?searchfilter=sticker | /v1/stickers/trending |
| Tenor | GIPHY | Notes |
|---|---|---|
/v2/categories | /v1/gifs/categories | GIPHY provides the category's featured GIF |
| Tenor | GIPHY |
|---|---|
/v2/autocomplete | /v1/gifs/search/tags |
Both require q parameter. Optional pagination parameters supported.
| Tenor | GIPHY |
|---|---|
/v2/search_suggestions (uses q) | /v1/tags/related/<term> |
| Tenor | GIPHY |
|---|---|
/v2/trending_terms | /v1/trending/searches |
| Tenor | GIPHY | Notes |
|---|---|---|
/v2/posts?ids=<ids> | /v1/gifs?ids=<ids> | Both use comma-separated list |
| — | /v1/gifs/:id | GIPHY also offers single-ID endpoint |
| Tenor | GIPHY |
|---|---|
/v2/search?random=true | /v1/gifs/random |
/v2/search?random=true&searchfilter=sticker | /v1/stickers/random |
Tenor returns a list of random results.
GIPHY returns a single random item.
| Tenor | GIPHY |
|---|---|
/v2/anonid | /v1/randomid |
Tenor uses a token-based pagination system with pos and next values.
GIPHY uses a simple numeric offset model with offset and limit.
Unlike Tenor, which uses opaque cursor tokens, GIPHY uses a simple numeric offset representing how many results to skip.
offset=25 returns results starting from the 26th itemlimit parameternext cursor// First pageconst response1 = await fetch('https://tenor.googleapis.com/v2/search?q=cats&key=KEY');const data1 = await response1.json();
// Next page using tokenconst response2 = await fetch(`https://tenor.googleapis.com/v2/search?q=cats&key=KEY&pos=${data1.next}`);// First pageconst response1 = await fetch('https://api.giphy.com/v1/gifs/search?q=cats&api_key=KEY&limit=25&offset=0');const data1 = await response1.json();
// Next page using numeric offsetconst response2 = await fetch('https://api.giphy.com/v1/gifs/search?q=cats&api_key=KEY&limit=25&offset=25');| Tenor Parameter | GIPHY Parameter | Values |
|---|---|---|
contentfilter | rating | Tenor: off | low | medium | high GIPHY: r | pg-13 | pg | g |
GIPHY defaults to r (restricted). Default and maximum rating can be configured for partners.
| Tenor | GIPHY | Notes |
|---|---|---|
anon_id | random_id | Can be supplied in every GIPHY request |
| Tenor | GIPHY |
|---|---|
media_filter (basic | minimal) | bundle (clips_grid_picker | messaging_non_clips | sticker_layering | low_bandwidth)OR fields parameter for fields on demand |
Example: fields=images.original.url,slug
| Tenor | GIPHY |
|---|---|
locale (xx_YY or YY) | lang + country_code |
GIPHY allows proxying user requests with:
country_coderegion_codeThe following parameters work the same way in both APIs:
q - Search term(s)Tenor centralizes tracking through /v2/registershare endpoint.
GIPHY decentralizes tracking with per-item analytics URLs.
Tenor uses a dedicated endpoint /v2/registershare where your backend logs share events directly.
Each GIPHY item includes an analytics object with event-specific URLs:
analytics.onload.url - Fire when item loadsanalytics.onclick.url - Fire when user clicksanalytics.onsent.url - Fire when message is sentTo register interactions:
random_id and ts (timestamp) parametersThe random_id should be obtained from the /v1/randomid endpoint and used consistently for that user across all API requests and analytics tracking.
// Log share event to backendawait fetch('https://tenor.googleapis.com/v2/registershare', { method: 'POST', body: JSON.stringify({ id: gifId, key: API_KEY, q: searchTerm })});// Get random_id from GIPHY (once per user, store for future use)const randomIdResponse = await fetch('https://api.giphy.com/v1/randomid?api_key=YOUR_API_KEY');const { random_id } = await randomIdResponse.json();
// Use this random_id for all API requests and analytics for this userconst searchResponse = await fetch(`https://api.giphy.com/v1/gifs/search?q=cats&api_key=YOUR_API_KEY&random_id=${random_id}`);const data = await searchResponse.json();
// Fire analytics URLs client-sideconst gif = data.data[0];const timestamp = Date.now();
// When GIF loadsfetch(`${gif.analytics.onload.url}?random_id=${random_id}&ts=${timestamp}`);
// When user clicksfetch(`${gif.analytics.onclick.url}?random_id=${random_id}&ts=${timestamp}`);
// When message is sentfetch(`${gif.analytics.onsent.url}?random_id=${random_id}&ts=${timestamp}`);| Platform | Response Structure |
|---|---|
| Tenor | • Uses standard HTTP status codes • Known errors return HTTP 200 with error field• Unknown errors use non-200 codes |
| GIPHY | • All responses include meta object• meta contains: status, msg, response_id• Errors indicated via meta.status• Some errors return empty data with 4xx status |
If meta.status=200, meta.response_id="" and body is empty, treat as an error.
Always validate data presence in GIPHY responses.
| Tenor | GIPHY |
|---|---|
results, next | data, pagination, meta |
Tenor uses next token; GIPHY uses offset + limit in the pagination object.
{ "results": [...], "next": "CAgQABokCOWW3Y8GMgYIChCAgBASJAj6z96PBjIGCAoQgJAQEiQIqqDejwYyBggKEIDgHA"}{ "data": [...], "pagination": { "total_count": 50000, "count": 25, "offset": 0 }, "meta": { "status": 200, "msg": "OK", "response_id": "abc123def456" }}| Code | Tenor Description | GIPHY Description | Migration Notes |
|---|---|---|---|
| 200 | OK or known error | OK; check meta | Validate data presence |
| 3xx | Redirect (rare) | Not documented | Ignore or handle normally |
| 400 | Not documented | Bad request | Validate params |
| 401 | Not documented | Unauthorized | Verify API key |
| 403 | Not documented | Forbidden | Ensure permissions |
| 404 | Not found | Not found | Show user-friendly message |
| 414 | Not documented | URI too long | Trim queries |
| 429 | Rate limit exceeded | Too many requests | 100 requests/hour beta limit |
| 5xx | Server error | Synthetic 200 | Retry with backoff |
| Tenor Field | GIPHY Equivalent | Notes |
|---|---|---|
id | id | String ID |
title | title | — |
content_description | title / alt_text | If available |
media_formats | images | Remap renditions (see below) |
created | create_datetime / update_datetime / import_datetime / trending_datetime | Unix timestamp → ISO 8601 |
tags | tags / featured_tags / user_tags | Partner-only |
| Tenor | GIPHY | Notes |
|---|---|---|
content_rating | rating | — |
url | url | Also: embed_url, source_post_url |
itemurl | bitly_url | Shareable short URL |
hasaudio | — | No equivalent |
hascaption | — | No equivalent |
flags | is_sticker / is_low_contrast | Boolean flags |
GIPHY user data includes username and public user info when available.
Use MP4 and WEBP formats where supported to maximize quality and reduce load time.
GIPHY provides more rendition options than Tenor for better optimization.
| Tenor Format | Typical Use | GIPHY Equivalent | Notes |
|---|---|---|---|
preview | Single-frame GIF | preview_gif / fixed_*_still | Preloading |
gif | Full GIF | original | Loops automatically |
mediumgif | Reduced GIF | downsized / downsized_medium | 2–5 MB |
tinygif | Small GIF | fixed_width / fixed_height | ~200px |
nanogif | Very small GIF | fixed_*_small | ~100px |
mp4 | Full MP4 | original.mp4 / hd.mp4 / 4k.mp4 | Plays once |
loopedmp4 | Looping MP4 | looping.mp4 | 15s loop |
tinymp4 | Reduced MP4 | fixed_*.mp4 | Mobile optimized |
nanomp4 | Smallest MP4 | fixed_*_small.mp4 / preview.mp4 | Low bandwidth |
webm | Video | original.webp (animated) | Not a video format |
| Tenor Format | GIPHY Equivalent | Notes |
|---|---|---|
webp_transparent | original.webp | Full quality WebP with transparency |
tinywebp_transparent | fixed_width.webp / fixed_height.webp | ~200px with transparency |
nanowebp_transparent | fixed_*_small.webp | ~100px with transparency |
gif_transparent | original.gif (stickers) | GIF with transparency layer |
tinygif_transparent | fixed_width.gif / fixed_height.gif | Small transparent GIF |
nanogif_transparent | fixed_*_small.gif | Smallest transparent GIF |
// GIPHY response structureconst gif = data.data[0];
// Original (highest quality)const originalUrl = gif.images.original.url;const originalMp4 = gif.images.original.mp4;
// Fixed width (responsive)const fixedWidth = gif.images.fixed_width.url;const fixedWidthMp4 = gif.images.fixed_width.mp4;
// Downsized (optimized)const downsized = gif.images.downsized.url;
// Preview (still frame)const previewGif = gif.images.preview_gif.url;
// For stickers with transparencyconst stickerWebp = gif.images.original.webp;Follow this checklist to ensure a smooth migration from Tenor to GIPHY:
tenor.googleapis.com to api.giphy.comkey parameter with api_key in all requests/v2/search → /v1/gifs/search/v1/stickers/search instead of searchfilter param/v2/featured → /v1/gifs/trending/v2/autocomplete → /v1/gifs/search/tags/v2/search?random=true → /v1/gifs/random/v2/posts → /v1/gifspos token → offset numeric valuecontentfilter → ratinganon_id → random_idlocale → lang + country_codemedia_filter → bundle or fieldsresults → datameta object validationnext token)media_formats → images/v2/registershare backend callsanalytics.onload.url when GIF loadsanalytics.onclick.url when user clicksanalytics.onsent.url when message sentrandom_id (from /v1/randomid endpoint) and ts to analytics URLs