Note: the docs contain some opinions. You can treat them as "best practices" for working with embeds if you'd like. Of course, they are still just our opinions.
On the highest level there are two types of embeds: oEmbed and "regular embed". What they are and how they differ is this: oEmbed is (or should be) specifically crafted by the website owners to look optimal for that website; good examples are Twitter and Facebook. In reality, oEmbeds come in all sizes and shapes and often may be far from optimal.
Regular embeds, however, will look uniform across the field. Note, that "regular embed" is not an official term. Instead, they cover everything else which is not an oEmbed. These comprise of The Open Graph protocol and web scraping techniques. Regular embeds are divided further into different content types; more on that in a bit.
Because of the issues with oEmbeds and the uniform characteristics of other regular embeds we recommend to avoid using oEmbeds. Our example code on the bottom of this page will have an implementation which reflects this. Note also that our API will return both oEmbed and regular embed in the response, whenever available. The regular embed should be available for most of the time, while oEmbed may or may not be.
oEmbeds have other problems as well — namely, the security isn't great. As it stands, oEmbed can contain any markup and thus injecting that into your site isn't safe, unless you really can trust the site. The safest way to present an oEmbed is to serve it in an iframe
. Some sites (Twitter, Facebook) can probably be trusted, and serving them directly without an iframe should be OK.
There are other security consideration as well and this one applies to regular embeds: Linking to 3rd party images directly opens up Cross-Site Request Forgery (CSRF) and other vulnerabilities, and it's advisable to host all the images (including favicons) through a your own domain. That domain can be called, for example, safe.yourdomain.com
, and it should be setup as a reverse proxy with NGINX to host the images. We will provide a sample NGINX configuration for that.
The API is simple; you will issue a GET request to our url, and will get a JSON document as a response. The API endpoint is as follows:
https://api.embed.rocks/api/?url=<url>
The API Key has to be provided in the headers as x-api-key
.
Here are some examples of calling the API. We are requestin the url https://www.bbc.com/sport/rugby-union/44325008
which translates to https%3A%2F%2Fwww.bbc.com%2Fsport%2Frugby-union%2F44325008
after encoding. Don't forget to encode, otherwise you may get nasty surprises!
$ curl -H "x-api-key:mykey" https://api.embed.rocks/api?url=https%3A%2F%2Fwww.bbc.com%2Fsport%2Frugby-union%2F44325008
The supplied url should be urlencoded. mykey
is your API key (you'll find it in Dashboard).
You can add &autoplay=1
to the query, which will make most videos (only flash videos, since you can/should control html5 videos yourself) autoplayable.
You can get extra data my adding &include=property1,property2
to the query:
include=article | Parses the article (using "Readability" type of login) from the html page |
include=source | The whole html source code |
include=oembed | oEmbed data |
Some fields can be left out from the response to speed up the processing and data the transfer:
skip=html | Don't add the helper "html" field. |
You can add &maxwidth=<number>
to the query, which will determine the maximum width of the oembed element.
As a response, you will get a JSON document with relevant data fields. Depending on the type of webpage you will get a different types of responses, as explained below:
There are a few fields that are common to all responses. These are:
Name | Value |
---|---|
type | rich | video | audio | photo |
url | The url you provided |
oembed | The oembed data as such |
html | A helper field to get you going faster. Includes some of the other fields (title. description, image). You can suppress this (by adding skip=html to the url) if you don't need this. |
error | If there was an error, this will be set (see below) |
Different types (rich, video, audio, photo) of responses have different fields, as show below:
These are probably the most common types of responses. This indicates, that the page contains text + images (although it may contain only text too).
Note, the value can contain {...}
which represents a JSON document. The value can also contain [...]
which represents an array. An array can contain {...}
(JSON documents).
Also note that the argument safe
is for displaying the image safely and securely – we will provide sample NGINX configuration for this.
Name | Value |
---|---|
type | rich |
url | The url you provided |
site | hostname of the site (www.example.com) |
title | Title of the page |
description | A short (250 characters) snippet of text; best describing the page |
author | Author's name (not always available) |
published_date | Not always available, but if it is, it will be: {
Note that sometimes the site provides only date, not time, and the published_date.parsed date will show 00:00:00 for the time. in those cases you should be suspicious and check the original time as well (which is provided as published_date.orig ). |
article | The whole article |
source | The whole page source (with the parameter include=source ) |
favicon | {
|
images | [ {
|
Name | Value |
---|---|
type | video |
url | The url you provided |
site | hostname of the site (www.example.com) |
title | Title of the page |
description | A short (250 characters) snippet of text; best describing the page |
favicon | {
|
videos | [ {
Note that we return videos of content type |
Name | Value |
---|---|
type | audio |
url | The url you provided |
site | hostname of the site (www.example.com) |
title | Title of the page |
description | A short (250 characters) snippet of text; best describing the page |
favicon | {
|
audios | [ {
|
Name | Value |
---|---|
type | photo |
url | The url you provided |
images | [ {
|
Name | Value |
---|---|
type | error |
error | Error object |
msg | Error message |
There is a backup API in case the primary API is down. The endpoint is:
https://api2.embed.rocks/api/
We suggest that you use this API for some specific sites, because the main API a 30 second timeout, and some sites may require more time. The sites that we know of are now just tiktok.com
.
The rich
type is probably the most common type; it contains usually an image and some text. The URL http://edition.cnn.com/2017/01/05/aviation/safest-airlines-for-2017/index.html
will get us the following JSON document:
{ "article": "<div>(article omitted in the example)</div>", "description": "AirlineRatings.com declares 2016 one of the safest years in aviation history as it names what it says are the world's safest airlines.", "favicon": { "safe": "http%3A%2F%2Fedition.cnn.com%2Ffavicon.ie9.ico", "url": "http://edition.cnn.com/favicon.ie9.ico" }, "images": [ { "safe": "http%3A%2F%2Fi2.cdn.cnn.com%2Fcnnnext%2Fdam%2Fassets%2F160412104516-airplane-generic-ii-super-tease.jpg", "url": "http://i2.cdn.cnn.com/cnnnext/dam/assets/160412104516-airplane-generic-ii-super-tease.jpg" } ], "site": "CNN", "title": "The world's safest airlines for 2017?", "type": "rich", "url": "http://edition.cnn.com/2017/01/05/aviation/safest-airlines-for-2017/index.html", "videos": [] }
It will look like this when rendered (you render this yourself, so this is just an example):
Here we will see a type of video
— the videos
array will contain items. The videos
array will be sorted roughly in the order of preference — the most preferrable type are first. These would be "HTML5" videos. Flash videos would be less preferrable and they would be placed in the end of the array. Our example code will handle these correctly.
{ "article": "<div><p>\n <strong>Pour évaluer une vidéo, vous devez la louer.</strong>\n </p></div>", "description": "A few pointers from my experience on things one should either do or avoid while in Finland. So some Finnish do's and don'ts! :) *On a side note, I know I hav...", "favicon": { "safe": "https%3A%2F%2Fs.ytimg.com%2Fyts%2Fimg%2Ffavicon-vflz7uhzw.ico", "url": "https://s.ytimg.com/yts/img/favicon-vflz7uhzw.ico" }, "html": "<div class=\"card\">\n <iframe style=\"width: 720px; height: 405px\" src=\"https://www.youtube.com/embed/9N0HB8vMJrI\"></iframe>\n \n <div class=\"card-text\">\n <h3>Do's and Don'ts in Finland | KatChats</h3>\n <p>A few pointers from my experience on things one should either do or avoid while in Finland. So some Finnish do's and don'ts! :) *On a side note, I know I hav...</p>\n \n <p class=\"fav\">\n <img onerror=\"this.style.display='none'\" class=\"favicon\" src=\"https://s.ytimg.com/yts/img/favicon-vflz7uhzw.ico\">\n YouTube\n </p>\n </div>\n \n </div>", "images": [ { "safe": "https%3A%2F%2Fi.ytimg.com%2Fvi%2F9N0HB8vMJrI%2Fmaxresdefault.jpg", "url": "https://i.ytimg.com/vi/9N0HB8vMJrI/maxresdefault.jpg" } ], "oembed": { "author_name": "KatChats", "author_url": "https://www.youtube.com/user/adamLsidney", "height": 270, "html": "<iframe width=\"480\" height=\"270\" src=\"https://www.youtube.com/embed/9N0HB8vMJrI?feature=oembed\" frameborder=\"0\" allowfullscreen></iframe>", "provider_name": "YouTube", "provider_url": "https://www.youtube.com/", "thumbnail_height": 360, "thumbnail_url": "https://i.ytimg.com/vi/9N0HB8vMJrI/hqdefault.jpg", "thumbnail_width": 480, "title": "Do's and Don'ts in Finland | KatChats", "type": "video", "version": "1.0", "width": 480 }, "site": "YouTube", "title": "Do's and Don'ts in Finland | KatChats", "type": "video", "url": "https://www.youtube.com/watch?v=9N0HB8vMJrI", "videos": [ { "height": "720", "secureUrl": "https://www.youtube.com/embed/9N0HB8vMJrI", "type": "text/html", "url": "https://www.youtube.com/embed/9N0HB8vMJrI", "width": "1280" }, { "height": "720", "secureUrl": "https://www.youtube.com/v/9N0HB8vMJrI?version=3&autohide=1", "type": "application/x-shockwave-flash", "url": "http://www.youtube.com/v/9N0HB8vMJrI?version=3&autohide=1", "width": "1280" }, { "height": "720", "type": "text/html", "url": "https://www.youtube.com/embed/9N0HB8vMJrI", "width": "1280" } ] }
Here we can decide if we want to render this as an oEmbed or a regular embed, because the JSON contains both. Below you can see how it looks like when rendered in both ways.
{ "url": "https://twitter.com/CatVlDEOS/status/1185826827333525504", "site": "Twitter", "favicon": { "url": "https://abs.twimg.com/icons/apple-touch-icon-192x192.png", "safe": "https%3A%2F%2Fabs.twimg.com%2Ficons%2Fapple-touch-icon-192x192.png" }, "title": "Cat Pics & Videos 🐈 on Twitter", "description": "Don't touch my toy hooman 😠 https://t.co/2Le3GSjORL", "images": [ { "url": "https://pbs.twimg.com/profile_images/1049047193083138049/3gLZVrmP.jpg", "safe": "https%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F1049047193083138049%2F3gLZVrmP.jpg", "width": 400, "height": 400 } ], "videos": [], "audios": [], "type": "rich", "oembed": { "url": "https://twitter.com/CatVlDEOS/status/1185826827333525504", "author_name": "Cat Pics & Videos 🐈", "author_url": "https://twitter.com/CatVlDEOS", "html": "<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">Don't touch my toy hooman 😠 <a href=\"https://t.co/2Le3GSjORL\">pic.twitter.com/2Le3GSjORL</a></p>— Cat Pics & Videos 🐈 (@CatVlDEOS) <a href=\"https://twitter.com/CatVlDEOS/status/1185826827333525504?ref_src=twsrc%5Etfw\">October 20, 2019</a></blockquote>\n<script async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\n", "width": 550, "height": null, "type": "rich", "cache_age": "3153600000", "provider_name": "Twitter", "provider_url": "https://twitter.com", "version": "1.0" }, "duration": 395, "html": "<div class=\"card\">\n <a target=\"_blank\" href=\"https://twitter.com/CatVlDEOS/status/1185826827333525504\">\n \n <img onerror=\"this.style.display='none'\" class=\"card-image\" src=\"https://pbs.twimg.com/profile_images/1049047193083138049/3gLZVrmP.jpg\">\n \n \n <div class=\"card-text\">\n <h3>Cat Pics & Videos 🐈 on Twitter</h3>\n <p>Don't touch my toy hooman 😠 https://t.co/2Le3GSjORL</p>\n \n <p class=\"fav\">\n <img onerror=\"this.style.display='none'\" class=\"favicon\" src=\"https://abs.twimg.com/icons/apple-touch-icon-192x192.png\">\n Twitter\n </p>\n </div>\n \n </a>\n </div>" }
Here is the oEmbed rendering:
Don't touch my toy hooman 😠 pic.twitter.com/2Le3GSjORL
— Cat Pics & Videos 🐈 (@CatVlDEOS) October 20, 2019
And here the regular:
You can control the image sizes with CSS. On the home page you saw the city card in two different sizes. The smaller had this CSS for the image:
.card img.card-image {float: left;max-width: 250px;max-height: 125px;}
The bigger had this CSS:
.card.card-big img.card-image {max-width: 100%;max-height: 600px;margin-bottom: 10px;}
You may also want to create square images, while maintaining the original aspect ratio, which you can do like this:
.card.square img.card-image {width: 150px;height: 150px;object-fit: cover;}
Word of caution; if the image is very tall and narrow or wide and shallow, the cropped area may not reflect what's in the image. Here's the example of the same aeroplane in a cropped square form:
Since our service returns the image sizes automatically, you can do something useful with that information: you can calculate a placeholder for the image, which will be correctly sized before the image has even been loaded. This way the card will have its final form right in the beginning, and there won't be any shuffling happening in the browser.
As an extra bonus, you can show the image with a fade-in effect, which kicks in when the image has actually been loaded. Note that this requires some JavaScript.
We have implemented the predefined size and the fade-in effect on this website. Also our example apps will have the source code for theses, so you can check how it is done and borrow the most useful parts into your application.
The fade-in effect requires some JavaScript. This means that the images are initially hidden and only shown when they are loaded (the load
event). The images will have opacity
set to 0
to make them hidden, and opacity
will be set to 1
in the image's load
event. In order to show the images at all times, even when JavaScript is disabled, you can put this code in the head
element of the page:
<noscript><style type="text/css">.card img.card-image {opacity: 1 !important;}</style></noscript>
You can get also screenshots with a different API endpoint:
https://api.embed.rocks/screenshot/?url=<url>&size=600x800
The screenshot is returned as base64 encoded PNG image, that is, as a string in the resulting JSON object.
You can put this straight into an IMG
like this: <img src="data:image/png;base64,RESULT-DATA-HERE">
tag or you can save it into disk after converting into binary (with JavaScript it would go like this: require('fs').writeFileSync('pic.png', Buffer.from(json.src, 'base64'));
).
The size
parameter is optional.
Your API key should be sent in the headers as x-api-key
.
We have two example apps for integration: one with plain JavaScript and one with a templating engine. Use whichever suits you best, or even both. You can use these as a starting point for your own implementation.
In case you want to use Nginx to serve images safely, here is our config file for that: NGINX.conf. You can find examples how to use that in the eintegration examples above.