Introduction
Welcome to the littlelogs API!
The API itself lives at https://littlelogs.co/api/1/. All requests (excepting OAuth authentication) use this as their base. Responses always return JSON.
To get started quickly you can use your browser, as the API supports session-based authentication. For example if you browse to https://littlelogs.co/api/1/logs/ you can view all public logs.
Authentication
Littlelogs uses OAuth2 to allow access to the API. You can register a new OAuth client app from the apps page of your account.
Once you have authenticated a user, you must authorise all requests to the API by including a header that looks like the following:
Authorization: Bearer NWRjMTA1Yjk3MWM2NzY0NWMwNjkyYzEy
Public vs confidential apps
When you’re creating a client app, you’ll be asked to choose which of these your app falls under. So which to choose?
Public clients are those that cannot reasonably expect to keep their client ID and secret secure, such as client-side Javascript applications, or native mobile apps whose contents could be dissembled. Confidential clients are generally server-side, and their code and client details are treated as secure.
In reality the only difference I can find in the OAuth2 spec is that confidential clients must send their client_secret during the authorisation flow,
and public apps do not.
OAuth2 authorisation flow
Send your user to the authorisation page at
https://littlelogs.co/oauth/authorize
# We can't really do this from the shell, but your URL would look like this:
curl https://littlelogs.co/oauth/authorize?response_type=code&client_id=[your_id]&redirect_uri=[your_uri]&scope=[your_scope]
# in django, we would do something like this
return redirect('https://littlelogs.co/oauth/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s' % (CLIENT_ID, REDIRECT_URI,"read+write"))
User authorises your client by hitting ‘Allow’, and Littlelogs returns the user to your
redirect_uriwithcode=[some_code]in the query string. Exchange your code for an access token:
curl -X POST https://littlelogs.co/oauth/token -d "grant_type=authorization_code" -d "code=[some_code]" -d "client_id=[your_id]" -d "client_secret=[your_secret]" -d "redirect_uri=[your_uri]"
import requests
url = 'https://littlelogs.co/oauth/token'
response = requests.post(url,
{'grant_type':'authorization_code',
'code':code,
'client_id':CLIENT_ID,
'client_secret':CLIENT_SECRET,
'redirect_uri':REDIRECT_URI })
Returns JSON if your request was successful:
{ "access_token": "122bb8707b6aee134e7746a40feca41868ddd578", "token_type": "Bearer", "expires_in": 31535999, "refresh_token": "ac45027ad037f53b3ce91be272b163f55a4a87e9", "scope": "read write read+write" }
The OAuth2 authorisation flow is vastly simpler than the original OAuth 1.0:
- Send your user to the “request authorisation” page at
/oauth/authorizewith these parameters:-
response_type=codeto request an auth code in return -
redirect_uriwith the URI to which littlelogs returns the user (must be HTTPS) -
scope=readorscope=read+writeto request read or read/write permissions -
client_idwhich is your OAuth2 client ID
-
- User authorises your application within the requested scopes (by hitting 'Allow’ in the browser)
- Exist returns the user to your
redirect_uri(GET request) with the following:-
codeparameter upon success -
errorparameter if the user didn’t authorise your client, or any other error with your request
-
- Exchange this code for an access token by POSTing to
/oauth/tokenthese parameters:-
grant_type=authorization_code -
codewith the code you just received -
client_idwith your OAuth2 client ID -
client_secretwith your OAuth2 client secret -
redirect_uriwith the URI you used earlier
-
- If successful you will receive a JSON object with an
access_token,refresh_token,token_type,scope, andexpires_intime in seconds.
Refreshing an access token
curl -X POST https://littlelogs.co/oauth/token -d "grant_type=refresh_token" -d "refresh_token=[token]" -d "client_id=[your_id]" -d "client_secret=[your_secret]"
import requests
url = 'https://littlelogs.co/oauth/token'
response = requests.post(url,
{'grant_type':'refresh_token',
'refresh_token':token,
'client_id':CLIENT_ID,
'client_secret':CLIENT_SECRET
})
Returns JSON if your request was successful:
{ "access_token": "122bb8707b6aee134e7746a40feca41868ddd578", "token_type": "Bearer", "expires_in": 31535999, "refresh_token": "ac45027ad037f53b3ce91be272b163f55a4a87e9", "scope": "read write read+write" }
Tokens expire in a month and can be refreshed at any time, invalidating the previous refresh and access tokens.
Request
POST /oauth/token
Parameters
| Name | Description |
|---|---|
refresh_token |
The refresh token previously received in the auth flow |
grant_type |
refresh_token |
client_id |
Your OAuth2 client ID |
client_secret |
Your OAuth2 client secret |
Response
The same as your original access token response, a JSON object with an access_token, refresh_token, token_type, scope, and expires_in time in seconds.
Signing requests
import requests
requests.post(url,
headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
# With curl, you can just pass the correct header with each request
curl "api_endpoint_here"
-H "Authorization: Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737"
Sign all authenticated requests by adding the Authorization header, Authorization: Bearer [access_token].
Requests
Paging lists
You can page through lists with limit and offset parameters, or by following the next and previous links within any paged response object.
Avoiding DELETE
If your client doesn’t support the DELETE method, you can pass _method=DELETE in a POST request to achieve the same result.
Timelines
Home timeline
{
"count": 1291,
"next": "https://littlelogs.co/api/1/timelines/home/?limit=20&offset=20",
"previous": null,
"results": [
{
"id": 2614,
"created": "2015-11-02T00:23:27Z",
"user": {
"id": 2,
"username": "belle",
"first_name": "Belle Beth",
"last_name": "Cooper",
"bio": "I'm a bit silly.",
"url": "http://bellebethcooper.com/",
"location": "Melbourne, AU",
"avatar": "https://littlelogs.co/static/media/avatars/belle.png",
"timezone": "Australia/Melbourne"
},
"source": null,
"content": "<p>Catching up on some fiddly <a class=\"tag\" href=\"/tag/admin\"><span class=\"hash\">#</span>admin</a> stuff this morning. Updated and backed up our <a class=\"tag\" href=\"/tag/HelloCode\"><span class=\"hash\">#</span>HelloCode</a> <a class=\"tag\" href=\"/tag/bookkeeping\"><span class=\"hash\">#</span>bookkeeping</a> docs, booked <a class=\"tag\" href=\"/tag/theatre\"><span class=\"hash\">#</span>theatre</a> tickets for this weekend, wrote up our company <a class=\"tag\" href=\"/tag/report\"><span class=\"hash\">#</span>report</a> for October, replied to some <a class=\"tag\" href=\"/tag/customerdevelopment\"><span class=\"hash\">#</span>customerdevelopment</a> questions about remote working, paid rent, updated <a class=\"tag\" href=\"/tag/exist\"><span class=\"hash\">#</span>exist</a> resource pages, and requested a refund for a bad app I bought recently.</p>",
"comments": [ ],
"tags": [
"admin",
"bookkeeping",
"customerdevelopment",
"exist",
"HelloCode",
"report",
"theatre"
],
"mentions": [ ],
"likes": [
{
"user": "mattimck",
"created": "2015-11-02T01:47:33Z"
}
]
}
]
}
A list of posts by the current user and others followed by the current user.
Request
GET /api/1/timelines/home/
Activity timeline
{
"count": 1495,
"next": "https://littlelogs.co/api/1/timelines/activity/?limit=20&offset=20",
"previous": null,
"results": [
{
"id": 6263,
"created": "2015-11-02T00:23:51Z",
"type": "comment_after",
"content": "rpgdan commented after you on a log",
"originator":
{
"id": 88,
"username": "rpgdan",
"first_name": "Dan",
"last_name": "",
"bio": "Game developer working in Hong Kong. My side project is a book about how to make a JRPG from scratch.",
"url": "http://howtomakeanrpg.com/",
"location": "Hong Kong",
"avatar": "https://littlelogs.co/static/media/avatars/rpgdan1441062803.png",
"timezone": "GMT"
},
"log": {
"id": 2613,
"created": "2015-11-01T23:46:55Z",
"user": {
"id": 88,
"username": "rpgdan",
"first_name": "Dan",
"last_name": "",
"bio": "Game developer working in Hong Kong. My side project is a book about how to make a JRPG from scratch.",
"url": "http://howtomakeanrpg.com/",
"location": "Hong Kong",
"avatar": "https://littlelogs.co/static/media/avatars/rpgdan1441062803.png",
"timezone": "GMT"
},
"source": null,
"content": "<p>113 lines edited this morning. </p>\n<p>In November, for the book, I'd like to:</p>\n<ul>\n<li>Get all outsourced art finished</li>\n<li>Finish a first edit pass through the Combat part of the book</li>\n</ul>\n<p>To do this I'm going to need to up my editing amount a little.</p>\n<p>October went quite well:</p>\n<ul>\n<li>Finished Part I of the book (probably the biggest part)</li>\n<li>Final art for Part II</li>\n<li>Grew the content on the site by one post a week</li>\n<li>Went from ~1k visitors to ~4k visitors to the site</li>\n<li>67 mailing list signups (there's no funnel from articles to book signup yet.)</li>\n</ul>\n<p>I didn't realise how big a project I was taking on when I started this. There's still quite a long way to go. Whatever happens I'm going to open up some access in January; purchasing early access or something similar.</p>",
"comments": [
SNIP
],
"tags": [],
"mentions": [],
"likes": [
SNIP
]
},
"comment": {
"id": 2540,
"created": "2015-11-02T00:23:51Z",
"user": SNIP ,
"content": "<p>Thanks guys :)</p>\n<p>I'll check out the book too!</p>",
"source": null,
"tags": [],
"mentions": []
}
}
]
}
I’ve snipped out a lot here for brevity, because even one notification (activity item) contains a lot.
You can see that a notification contains a type, a plain-text content field describing the notification,
the originator user object,
and then the full related log and comment objects. There should be enough within to reassemble the original log
without any other requests. Not all notification types will have an attached comment, but each should have a log.
Request
GET /api/1/timelines/activity/
Public timeline
See all public logs.
Users
Get the current user
{
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "Making this thing right now.",
"url": "http://hellocode.co/",
"location": "Melbourne, AU",
"avatar": "https://littlelogs.co/static/media/avatars/josh.png",
"timezone": "Australia/Melbourne"
}
If you’re not quite sure who you are, this endpoint will return the currently authenticated user.
Request
GET /api/1/users/$self/
View a user
{
"id": 120,
"username": "belle",
"first_name": "Belle Beth",
"last_name": "Cooper",
"bio": "Writer and coder of things",
"url": "http://bellebethcooper.com",
"location": "Melbourne",
"avatar": "http://littlelogs.co/static/media/avatars/belle.png",
"timezone": "UTC"
}
Request
GET /api/1/users/<username>/
List follows for a user
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "Making this thing right now.",
"url": "http://hellocode.co/",
"location": "Melbourne, AU",
"avatar": "https://littlelogs.co/static/media/avatars/josh.png",
"timezone": "Australia/Melbourne"
},
]
}
A list of other users who are followed by this user.
Request
GET /api/1/users/followed-by/<username>/
List all following a user
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "Making this thing right now.",
"url": "http://hellocode.co/",
"location": "Melbourne, AU",
"avatar": "https://littlelogs.co/static/media/avatars/josh.png",
"timezone": "Australia/Melbourne"
}
]
}
A list of other users who follow this user.
Request
GET /api/1/users/following/<username>/
Follow a user
Request
POST /api/1/users/<username>/follow/
Response
Returns 201 CREATED if successful, but no content.
Unfollow a user
Request
DELETE /api/1/users/<username>/unfollow/
Response
Returns 204 NO CONTENT if successful, but no content.
Logs
List all public logs
{
"count": 2402,
"next": "https://littlelogs.co/api/1/logs/?limit=20&offset=20",
"previous": null,
"results": [
{
"id": 2617,
"created": "2015-11-02T05:39:38Z",
"user": {
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "Making this thing right now.",
"url": "http://hellocode.co/",
"location": "Melbourne, AU",
"avatar": "https://littlelogs.co/static/media/avatars/josh.png",
"timezone": "Australia/Melbourne"
},
"source": null,
"content": "<p>Whew! Spent ages collecting our <a class=\"tag\" href=\"/tag/exist\"><span class=\"hash\">#</span>exist</a> <a href=\"https://exist.io/blog/weekly-links-102-light/\">links post</a> for this week. We're changing tack and making each one a collection of articles and apps/devices based on a theme, which will culminate in a post <a class=\"content-user\" href=\"/belle\">@belle</a> writes that fits in with the recent themes. This one is about light — sunlight, too much light at night, etc., and tools around that. <a class=\"tag\" href=\"/tag/weeklylinks\"><span class=\"hash\">#</span>weeklylinks</a></p>",
"comments": [],
"tags": [
"exist",
"weeklylinks"
],
"mentions":[
"belle"
],
"likes": []
}
]
}
This is the same as the public timeline.
Request
GET /api/1/logs/
List logs for a tag
{
"count": 12,
"next": "https://littlelogs.co/api/1/logs/tag/blogging/?limit=20&offset=20",
"previous": null,
"results": [
{
"id": 2588,
"created": "2015-10-29T04:28:49Z",
"user": {
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "Making this thing right now.",
"url": "http://hellocode.co/",
"location": "Melbourne, AU",
"avatar": "https://littlelogs.co/static/media/avatars/josh.png",
"timezone": "Australia/Melbourne"
},
"source": null,
"content": "<p>Finally posted my thoughts from my earlier ReadWrite interview onto the Hello Code blog. Barely any of what I wrote ended up in the final piece (which is always the way) so I figured I'd repurpose it as a blog post rather than letting all those words go to waste. It's on <a href=\"http://blog.hellocode.co/post/future-activity-data/\">the future of activity tracking data</a>. <a class=\"tag\" href=\"/tag/blogging\"><span class=\"hash\">#</span>blogging</a></p>",
"comments": [ ],
"tags": [
"blogging"
],
"mentions": [ ],
"likes": [
{
"user": "kiriappeee",
"created": "2015-10-29T04:51:11Z"
},
{
"user": "belle",
"created": "2015-10-29T08:31:38Z"
}
]
}
]
}
Request
GET /api/1/logs/tag/<tag>/
List logs by a user
Request
{
"count": 65,
"next": "https://littlelogs.co/api/1/logs/user/kateo/?limit=20&offset=20",
"previous": null,
"results": [
{
"id": 2606,
"created": "2015-11-01T04:38:41Z",
"user": {
"id": 45,
"username": "kateo",
"first_name": "Kate ",
"last_name": "",
"bio": "",
"url": "",
"location": "Melbourne",
"avatar": "https://littlelogs.co/static/media/avatars/kateo1430996134.png",
"timezone": "Australia/Melbourne"
},
"source": null,
"content": "<p>I completed all of the user login system lessons but it doesn't work properly. </p>\n<ul>\n<li>After logging in it displays the login page instead of the members page</li>\n<li>Logout isn't displayed after logging in </li>\n<li>Register is shown to logged in users</li>\n</ul>",
"comments": [
{
"id": 2521,
"created": "2015-11-01T04:49:59Z",
"user": {
"id": 45,
"username": "kateo",
"first_name": "Kate ",
"last_name": "",
"bio": "",
"url": "",
"location": "Melbourne",
"avatar": "https://littlelogs.co/static/media/avatars/kateo1430996134.png",
"timezone": "Australia/Melbourne"
},
"content": "<p>all fixed, that was quick. </p>\n<p>Notes to self:\n- spell correctly, flase and false are not the same\n- getUserbyID must be getUserById</p>",
"source": null,
"tags": [ ],
"mentions": [ ]
}
],
"tags": [ ],
"mentions": [ ],
"likes": [ ]
}
]
}
GET /api/1/logs/user/<username>/
View a log
{
"id": 2588,
"created": "2015-10-29T04:28:49Z",
"user": {
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "Making this thing right now.",
"url": "http://hellocode.co/",
"location": "Melbourne, AU",
"avatar": "https://littlelogs.co/static/media/avatars/josh.png",
"timezone": "Australia/Melbourne"
},
"source": null,
"content": "<p>Finally posted my thoughts from my earlier ReadWrite interview onto the Hello Code blog. Barely any of what I wrote ended up in the final piece (which is always the way) so I figured I'd repurpose it as a blog post rather than letting all those words go to waste. It's on <a href=\"http://blog.hellocode.co/post/future-activity-data/\">the future of activity tracking data</a>. <a class=\"tag\" href=\"/tag/blogging\"><span class=\"hash\">#</span>blogging</a></p>",
"comments": [ ],
"tags": [
"blogging"
],
"mentions": [ ],
"likes": [
{
"user": "kiriappeee",
"created": "2015-10-29T04:51:11Z"
},
{
"user": "belle",
"created": "2015-10-29T08:31:38Z"
}
]
}
Request
GET /api/1/logs/<id>/
Create a log
Request
POST /api/1/logs/create/
Parameters
| Name | Description |
|---|---|
content |
Markdown content of the log |
Response
Returns 201 CREATED if successful, and the new log.
Preview log content
Request
POST /api/1/logs/preview/
Parameters
| Name | Description |
|---|---|
content |
Markdown content of the log |
Response
Returns 200 OK if successful, and the parsed HTML content of the log.
Delete a log
Request
DELETE /api/1/logs/<id>/delete/
Response
Returns 204 NO CONTENT if successful, but no content.
Comments
Create a comment
Request
POST /api/1/logs/<id>/comment/
Parameters
| Name | Description |
|---|---|
content |
Markdown content of the log |
Response
Returns 201 CREATED if successful, and the updated parent log.
Delete a comment
Request
DELETE /api/1/comment/<id>/delete/
Response
Returns 204 NO CONTENT if successful, but no content.
Likes
Like a log
Request
POST /api/1/logs/<id>/like/
Response
Returns 201 CREATED if successful, and the updated log.
Unlike a log
Request
DELETE /api/1/logs/<id>/unlike/
Response
Returns 204 NO CONTENT if successful, but no content.
Tags
List all tags
{
"count": 1136,
"next": "https://littlelogs.co/api/1/tags/?limit=20&offset=20",
"previous": null,
"results": [
{
"name": "52filmsbywomen",
"created": "2015-10-17T13:17:37Z"
},
{
"name": "7dayproject",
"created": "2015-05-14T11:57:17Z"
},
{
"name": "87transit",
"created": "2015-10-21T05:01:27Z"
},
{
"name": "accent",
"created": "2015-03-01T22:47:26Z"
},
{
"name": "accessibility",
"created": "2015-02-28T01:24:41Z"
},
{
"name": "accord",
"created": "2015-09-10T16:24:25Z"
},
{
"name": "accountant",
"created": "2015-06-26T00:12:45Z"
}
]
}
Request
GET /api/1/tags/
List tags for a user
{
"count": 116,
"next": "https://littlelogs.co/api/1/tags/user/josh/?limit=20&offset=20",
"previous": null,
"results": [
{
"name": "acting",
"created": "2015-02-07T08:18:25Z"
},
{
"name": "admin",
"created": "2015-02-03T23:31:51Z"
},
{
"name": "android",
"created": "2015-02-04T11:40:06Z"
},
{
"name": "ansible",
"created": "2015-02-26T06:11:45Z"
},
{
"name": "API",
"created": "2015-04-18T12:59:04Z"
},
{
"name": "blogging",
"created": "2015-06-27T03:01:59Z"
}
]
}
GET /api/1/tags/user/<username>/
List trending tags
[
{
"name": "makerbase",
"created": "2015-10-14T19:52:48Z"
},
{
"name": "littlelogs",
"created": "2015-02-03T06:49:32Z"
},
{
"name": "exist",
"created": "2015-02-03T07:40:34Z"
},
{
"name": "API",
"created": "2015-04-18T12:59:04Z"
},
{
"name": "outduo",
"created": "2015-10-09T09:22:39Z"
},
{
"name": "content",
"created": "2015-02-03T07:40:27Z"
},
{
"name": "replylater",
"created": "2015-10-17T04:19:43Z"
},
{
"name": "writing",
"created": "2015-02-15T14:54:07Z"
},
{
"name": "Ghost",
"created": "2015-07-14T08:25:21Z"
},
{
"name": "curo",
"created": "2015-02-25T21:30:45Z"
}
]
Request
GET /api/1/tags/trending/