Data API
The Data API returns the public data of the blog.
- No API keys are required.
- All responses are in the JSON format.
- All endpoints use the HTTP
GETmethod. - The base path is:
https://blogs.hyvor.com/api/data/v0/{subdomain} - For example, if your blog is at
https://example.hyvor.com, the base path ishttps://blogs.hyvor.com/api/data/v0/example- Replace
{subdomain}with the subdomain of your blog.
- Replace
In addition to calling the Data API via HTTP, it is possible call it within template files using
the Twig data() function. It is the preferred
method if you want data to render some UI (Ex: recent posts section) in your blog, because the data() function calls the Data API internally at the time of rendering the template,
eliminating the need for additional HTTP requests.
Endpoints
Single-object
/post- a post/page/tag/author/blog- blog settings
Multi-object
/posts/posts/search- search posts/tags/authors
Response
For single-object endpoints, the response is an object. For example, /post endpoint returns a Post object (See below for object definitions).
// A Post Object
{
"id": 1000,
"slug": "post",
...
}For multi-object endpoints, the response looks like this:
{
"data": [{}, {}], // array of objects
"pagination": {} // a Pagination object
}Request
Single-Object endpoints
For /post, /tag, and /author
idintegerEither the id or the slug is required for those endpoints.
The /blog endpoint only takes language and keys as an input.
Multi-Object endpoints
/posts, /posts/search, /tags, and /authors
The /posts/search endpoint has a required search param in addition to the above params.
searchThe /tags endpoint has an optional visibility param to filter tags by visibility. Note that private tags are not meant to be shown in the blog publicly. They should only be used for internal purposes (ex: show/hide a widget in the blog if the tag is present in the post).
visibilitypublic - only public tags, private - only private tags, any - all tagspublic1. language param
If your blog has multiple languages, you can set the language param to a language code (ex: en, fr) of a language in your blog. If this param is not provided, the primary language of the blog is used. That language will be used to localize strings in posts, authors, tags, and the blog.
Note: There is an important distinction between posts (/post, /posts, and /posts/search) and other endpoints when using languages.
Let's say you have two languages in your blog: en (primary) and fr. If you call the /posts endpoint with language the language code fr, only the posts that have a fr variant will be returned. However, in
other endpoints (authors, tags), all records will be returned regardless of they have a fr variant or not. Missing translations will be filled with primary language strings. The reason is that,
when someone visits your blog's /fr index page, we only want to show the posts that
are translated into French. We do not want to "fallback" post contents. However, fallbacking
author/tags data is fine in most cases.
language in post(s) endpoints works as a filter, while it works as a
translator in other endpoints. 2. limit param
The limit param can be used to limit the number of records returned in multi-object endpoints. The default is 25. Max is 250.
3. page param
The page param can be used to paginate results. This works in combination with the limit param. The default value is 1.
To get the first 20 results: /posts?limit=20 To get the next 20 results (page 2):
/posts?limit=20&page=24. filter param
Example: (published_at > 1639665890 & published_at < 1639695890) | is_featured=true
Our Data API uses Laravel FilterQ under the hood, which allows you to write advanced logic like the above example, using comparison and logical operators.
A condition consists of three parts:
keyoperatorvalue
Operators
=- equals!=- not equals>- greater than<- less than>=- greater than or equals<=- less than or equals
Values
null- bool:
trueorfalse - string:
'hello'orhello- Strings without quotes should match
[a-zA-Z_][a-zA-Z0-9_-]+and cannot betrue,false, ornull.
- Strings without quotes should match
- numbers:
250,-250,2.5
Logical Operators
You can use Logical Operators to combine multiple conditions.
|- OR&- AND
Please see the FilterQ Expressions documentation if you need more details.
Supported Keys for Filtering
/postsidintegeris_featured=, !=booleanslug=, !=stringfeatured_image_url=, !=nullcanonical_url=, !=stringwordsintegertag.idintegertag.slug=, !=stringauthor.idintegerauthor.slug=, !=string/tags and /authorsidintegerslug=, !=stringpost_countintegerDate Values
Here are some valid values for date keys.
'2022-01-01''yesterday''first day of this year''last day of next month''+1 day''-1 week''next Thursday'1639655890- UNIX Timestamp
For example: In /posts endpoint, you may use published_at>'-7 days' to get posts published in the last 7 days.
Filtering Examples
Note that when calling the API via HTTP, the filter value should be URL-encoded.
To get posts authored by Alex:
// filter
author.slug=alex
// URL-encoded
/posts?filter=author.slug%3DalexTo get featured posts:
// filter
is_featured=true
// URL-encoded
/posts?filter=is_featured%3DtrueTo get posts with either the tag audio or video:
// filter
tag.slug=audio|tag.slug=video
// URL-encoded
/posts?filter=tag.slug%3Daudio%7Ctag.slug%3DvideoTo get tags that have at least 5 posts:
// filter
posts_count>=5
// URL-encoded
/tags?filter=posts_count%3E%3D5To get authors who are added after January 1st 2020:
// filter
created_at>='2020-01-01'
// URL-encoded
/authors?filter=created_at%3E%3D%272020-01-01%27To get posts published in the last 7 days.
// filter
published_at>'-7 days'
// URL-encoded
/posts?filter=published_at%3E%27-7%20days%275. sort param
Here’s a list of supported sort values. You can combine multiple as comma-separated-values, which then will be executed in its order, similar to ORDER BY in SQL.
/posts Default published_at DESCpublished_atcreated_atupdated_atidis_featuredtitlewords/tags and /authors Default posts_count DESCpost_countcreated_atThe default sort method is DESC. Here are some examples for the sort param.
published_at- sorted by published_at in descending orderpublished_at ASC- sorted by published_at in ascending orderis_featured DESC,published_at DESC- featured posts first, then ordered by publish time in descending order.DESCis optional.is_featured,published_atis identical with the former.
6. keys param
The keys can be used to include or exclude keys from the Objects, similar to GraphQL. All endpoints support the keys param.
If you call the /posts endpoint, with keys=id,content, the post objects will only contain those two keys.
{
"id": 1000,
"content": "<p></p>"
}Use ! at the start to exclude tags. For example, keys=!content,description will exclude content and description from the Post object and all other keys will be included.
Let’s say you only want to get the post ID and tag ID of the posts. Use keys=id,tags.id. You will get objects like this.
{
"id": 1000,
"tags": [
{
"id": 2000
}
]
}Objects
All timestamps are in Unix Timestamp format (integer).
Post Object
{
"id": 1000,
"created_at": 1639655890,
"updated_at": 1639655890,
"published_at": 1639665890,
"is_featured": false,
"is_page": false,
"slug": "hello-world",
"content": "<p></p>",
"title": "Hello World",
"description": "This is a hello world page",
"url": "https://subdomain.hyvorblogs.io/hello-world",
"featured_image_url": "https://example.com/image.png",
"canonical_url": null,
"words": 500,
"code_head": "",
"code_foot": "",
"language": language object,
"variants": [ variant objects ],
"tags": [ tag objects ],
"tags_private": [ tag objects ],
"authors": [ author objects ]
}idintegercreated_atintegerupdated_atintegerpublished_atintegeris_featuredbooleanslugstringurlstringtitlestringdescriptionstring | nullfeatured_image_urlstring | nullcanonical_urlstring | nullwordsintegerIn posts, id attribute is globally unique within Hyvor Blogs. The slug attribute is unique within the blog.
Tag Object
{
"id": 2000,
"created_at": 1639655890,
"is_private": false,
"name": "Hello World",
"description": "Saying hello to the world",
"slug": "hello-world",
"url": "https://subdomain.hyvorblogs.io/tag/hello-world",
"posts_count": 20,
"code_head": null,
"code_foot": "<p>some code</p>",
"language": language object,
"variants": [ variant objects ],
}idintegercreated_atintegernamestringdescriptionstring | nullslugstring/tag/{slug})urlstringposts_countintegerAuthor Object
Author is a user who has written at least one post
{
"id": 3000,
"created_at": 1639655890,
"slug": "blogger",
"url": "https://subdomain.hyvorblogs.io/author/blogger",
"name": "Blogger",
"picture_url": "https://example.com/image.png",
"bio": "I am a blogger",
"website_url": "https://example.com",
"location": "France",
"social": social media object,
"posts_count": 32,
"language": language object,
"variants": [ variant objects ],
}idintegercreated_atintegerslugstring/author/{slug})urlstringnamestringpicture_urlstring | nullbiostring | nullwebsite_urlstring | nulllocationstring | nullposts_countintegerBlog Object
{
"subdomain": "alex",
"name": "My Blog",
"description": "This is my blog hosted on Hyvor Blogs",
"logo_url": "https://blog.hyvorblogs.io/media/logo.png",
"icon_url": "https://blog.hyvorblogs.io/media/icon.png",
"cover_url": "https://blog.hyvorblogs.io/media/cover.png",
"url": "https://blog.hyvorblogs.io",
"social": social media object,
"nav_header": [
{
"name": "Home",
"url": "/"
},
{
"name": "About",
"url": "/about"
}
],
"nav_footer": [
{
"name": "Privacy",
"url": "/privacy"
}
],
"languages": [ language objects ],
"code_head": "",
"code_foot": "",
"posts_count": 200,
// the following are blog settings
// which are used for generating header code, color themes
// and footer branding
"seo_indexing": true,
"color_modes": "light",
"color_mode_default": "light",
// for cache busting
"cache_version_styles": 1,
}subdomainstringnamestringdescriptionstringlogo_urlstring | nullcover_urlstring | nullurlstring | nullbase_urlstringnav_header, nav_footerarray of objectscode_head, code_footstring</head>, and </body> for all pages.posts_countintegerLanguage Object
{
"id": 1000,
"code": "en",
"name": "English",
"is_primary": true,
"direction": "ltr"
}idintegercodestringnamestringis_primarybooleandirectionstringltr or rtlVariant Object
A variant object contains data of a language variant of a post, tag, or an author.
{
"language": {
"id": 1001,
"code": "fr",
"name": "French",
"is_primary": false,
"direction": "ltr"
},
"url": "https://subdomain.hyvorblogs.io/fr/hello-world"
}Pagination Object
A pagination object is included in all multi-object endpoints (/posts, /authors, /tags).
{
"total": 100,
"pages": 10,
"limit": 5,
"page": 1,
"page_prev": null,
"page_next": 2,
}totalintegerpagesintegerpages = round_to_upper(total/limit)limitintegerpageintegerpage_previnteger or stringnull if no previous pages)page_nextinteger or stringnull if no next pages)Social Media Object
{
"facebook": null,
"twitter": "https://twitter.com/HyvorBlogs",
"linkedin": "https://www.linkedin.com/company/30240435",
"youtube": null,
"instagram": null,
"github": "https://github.com/hyvor",
"tiktok": null
}Error Handling
In case of an error, the HTTP status code will be a non-200 status code.
For 4xx errors, the response will be a JSON object.
{
"error": "ID is required",
"error_code": "422"
}These HTTP codes are possible:
404 Not Found - Resource not found - 404 can be returned in a single-object endpoints when the object is not found - Make sure ID/slug (and language for posts) is correct- 422 Unprocessable Entity - Invalid input
- Check the query params
- You can find more details in the JSON output of the error
5xx errors means something is wrong on our side. Check our status page for any downtimes. If the issue persists, contact us.
Pages
We do not have separate endpoints to fetch Pages.
- To get a single page, call the
/postendpoint with the page ID or slug. - To get multiple pages, call the
/postsendpoint with?pages=trueparam. /posts/searchdoes not support searching pages.