Introduction
Welcome to the Larder API. Using this API you can create read/write clients for both your own and your users’ bookmarks.
Getting started
- Check out the authentication section below to determine if you need to grab a simple one-off token, or create an OAuth2 client for many users.
- Create a client or grab your simple token from the site, then use your new credentials to start requesting authenticated endpoints.
- Need help? You can always email us at hello@larder.io if you’re stuck or need to clarify something.
Quick tips:
- Responses are always in JSON
- POST data can be encoded as JSON or your standard
application/x-www-form-urlencoded
(ie. just sending a set of parameters) - All requests require authentication
- Requests are rate limited to 600 requests/hour/user which you probably won’t ever even notice
Authentication
There are two authentication methods — simple tokens, and OAuth2 clients. So which one do you need?
Simple token authentication exists as a basic means for users to access their own data from Larder. This is only recommended for quickly building a single-user client for yourself where OAuth2 is overkill. If you intend to ask other users to authenticate with your client, do not use this method — that’s what OAuth2 is for.
OAuth2 clients are superior to simple-token authentication when authenticating many users. One client application can create and use access tokens for many users. One user may have many clients authorised to access their Exist account, each with a separate token that can be revoked.
You can create a client from your app management page within your account.
Simple token authentication
All endpoints require authentication. For quick personal hacking purposes we provide a simple token-based scheme with a single token per user.
Make sure this token is included in your requests by including the Authorization: Token [token]
header with every request.
If you are logged in to Larder in the browser your session-based authentication will also work. This is handy for browsing the API (assuming you’ve set up your browser to accept JSON) but shouldn’t be relied on for programmatic access.
Your token is displayed on your app management page. You cannot exchange user credentials for a token with this method — if you’d like to make a client for others to use, please use OAuth2.
Signing requests
Include the “Authorization: Token xyz” header in all requests.
import requests
requests.post(url,
headers={'Authorization':'Token 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
# With curl, you can just pass the correct header with each request
curl "api_endpoint_here"
-H "Authorization: Token 96524c5ca126d87eb18ee7eff408ca0e71e94737"
Include the Authorization: Token [your_token]
header in all requests.
OAuth2 authentication
Make sure you have created a client within Larder, so you’ve received client credentials. You can create a client from your app management page within your account.
All endpoints require authentication, except those that are part of the OAuth2 authorisation flow.
Make sure your access token is included in your requests by including the Authorization: Bearer [token]
header with every request.
You may mix and match OAuth2 authentication with simple token or even session-based authentication as you test the API. API endpoints will respond with JSON within your browser if you are logged in to the site.
Authorisation flow
Send your user to the authorisation page at
https://larder.io/oauth/authorize/
# We can't really do this from the shell, but your URL would look like this:
curl https://larder.io/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://larder.io/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 Larder returns the user to your
redirect_uri
withcode=[some_code]
in the query string. Exchange your code for an access token:
curl -X POST https://larder.io/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://larder.io/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": 2591999,
"refresh_token": "ac45027ad037f53b3ce91be272b163f55a4a87e9",
"scope": "read write read+write"
}
The OAuth2 authorisation flow is a bit of a pain, but still vastly simpler than the original OAuth 1.0.
- Send your user to the “request authorisation” page at
/oauth/authorize/
with these parameters in the query string:-
response_type=code
to request an auth code in return -
redirect_uri
with the URI to which Larder returns the user (must be HTTPS) -
scope=read
orscope=read+write
to request read or read/write permissions -
client_id
which is your OAuth2 client ID
-
- User authorises your application within the requested scopes (by hitting 'Allow’ in the browser)
- Larder returns the user to your
redirect_uri
(as a GET request) with the following:-
code
parameter upon success -
error
parameter 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/token/
these parameters (use form data, not JSON):-
grant_type=authorization_code
-
code
with the code you just received -
client_id
with your OAuth2 client ID -
client_secret
with your OAuth2 client secret -
redirect_uri
with the URI you used earlier
-
- If successful you will receive a JSON object with an
access_token
,refresh_token
,token_type
,scope
, andexpires_in
time in seconds.
Refreshing an access token
curl -X POST https://larder.io/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://larder.io/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": 2591999,
"refresh_token": "ac45027ad037f53b3ce91be272b163f55a4a87e9",
"scope": "read write read+write"
}
Tokens expire in a month and can be refreshed for a new access token at any time, invalidating the original access and refresh tokens.
Request
POST /oauth2/access_token
Parameters
These must be regular form data, not JSON.
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 "https://larder.io/api/1/@me/user/" \
-H "Authorization: Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737"
Sign all authenticated requests by adding the Authorization header, Authorization: Bearer [access_token]
.
Note that this differs from the simple token-based authentication by using Bearer
, not Token
.
User
import requests
requests.get("https://larder.io/api/1/@me/user/",
headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
Returns JSON:
{
"id": 100,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"timezone": "Australia/Melbourne",
"avatar": "https://larder.io/static/media/avatars/josh.png",
"date_joined": "1992-01-10T03:55:09Z",
"links": 1337,
"is_trial": false
}
Get the authenticated user’s details.
GET https://larder.io/api/1/@me/user/
Folders
Get all folders
import requests
requests.get("https://larder.io/api/1/@me/folders/",
headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
curl "https://larder.io/api/1/@me/folders/" -H "Authorization: Bearer xyz"
Returns JSON:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "cf44505b-b235-4038-9d8a-3070489e97c8",
"name": "Code",
"color": "58db4e",
"icon": null,
"created": "2016-02-16T09:54:39Z",
"modified": "2017-02-11T12:02:07Z",
"parent": "42d1a97e-8e1b-420f-b51c-a49a69cb183a",
"folders": [],
"links": 19
}
]
}
This returns a paged results of all folders for the authenticated user.
HTTP Request
GET https://larder.io/api/1/@me/folders/
Parameters
Name | Description |
---|---|
limit |
Optional number of folders to return per page. Default 20. |
offset |
Number of previous items to offset the returned page by. Optional, use in conjunction with limit . |
Response
Returns a paged list of folders. Each folder contains the ID of its parent
folder, and its child folders
, so
you can assemble the tree structure of folders. Folders may have a null parent
which denotes them as top-level.
Get a specific folder
See get all bookmarks for a folder.
Add a folder
Creates a new folder where you can keep some good bookmarks.
HTTP Request
POST https://larder.io/api/1/@me/folders/add/
Parameters
Name | Description |
---|---|
name |
String (max 120 chars) representing the folder’s name. |
parent |
UUID (the id field) of the folder that’ll be the parent of this one. Send this as null for a top-level folder. |
Response
Returns a HTTP 201 status and the saved JSON folder object if all went well. Otherwise
you’ll get a HTTP 400 status and a JSON object with an error
field detailing the error. You may also get a 404
if the parent ID is incorrect.
Edit a folder
Updates the fields on a folder.
HTTP Request
POST https://larder.io/api/1/@me/folders/:id/edit/
Parameters
Name | Description |
---|---|
name |
String (max 120 chars) representing the folder’s name. |
parent |
UUID (the id field) of the folder that’ll be the parent of this one. Send this as null for a top-level folder. |
Response
Returns a HTTP 200 status and the saved JSON folder object if all went well. Otherwise
you’ll get a HTTP 400 status and a JSON object with an error
field detailing the error. You may also get a 404
if the parent ID is incorrect.
Delete a folder
Removes a folder entirely, and optionally its contents too. It’s all really truly deleted from the database, so beware. You may choose to move the contents of the folder to another folder.
HTTP Request
POST https://larder.io/api/1/@me/folders/:id/delete/
Parameters
Name | Description |
---|---|
empty_to |
UUID of the folder you’d like to move the contents of this folder to. This is required but can be explicitly set to null if you’re sending JSON, or an empty string for x-www-form-urlencoded data, in which case bookmarks will be deleted. |
Response
Returns a HTTP 204 status and no content if all went well. Otherwise you’ll get a HTTP 404 status if it couldn’t be found.
Bookmarks
Get all bookmarks for a folder
import requests
requests.get("https://larder.io/api/1/@me/folders/cf44505b-b235-4038-9d8a-3070489e97c8/",
headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
curl "https://larder.io/api/1/@me/folders/cf44505b-b235-4038-9d8a-3070489e97c8/" \
-H "Authorization: Bearer xyz"
Returns JSON:
{
"count": 19,
"next": null,
"previous": null,
"results": [
{
"id": "2fac2637-a4ab-4b03-be56-224d3608742f",
"tags": [
{
"id": "c1e3ced3-2702-4b49-a771-2d070d3b1c0e",
"name": "java",
"color": "8fcb00",
"created": "2016-02-12T11:41:41Z",
"modified": "2016-05-14T07:53:48Z"
}
],
"title": "square/picasso",
"description": "A powerful image downloading and caching library for Android",
"url": "https://github.com/square/picasso",
"domain": "github.com",
"created": "2016-02-12T12:10:39Z",
"modified": "2016-12-01T05:44:18Z",
"meta": {
"commit": {
"date": "2016-11-01T13:51:32Z",
"message": "Merge pull request #1522 from NightlyNexus/eric/remoteviews-callback",
"author": "JakeWharton"
},
"type": "github.com",
"release": {
"description": "Initial release.",
"name": "picasso-parent-1.0.0",
"published_at": "2013-08-30T23:28:03Z",
"author": "JakeWharton"
}
}
}
]
}
Requesting a specific folder returns a paged list of the folder’s bookmark contents.
HTTP Request
GET https://larder.io/api/1/@me/folders/:id/
Parameters
Name | Description |
---|---|
limit |
Optional number of bookmarks to return per page. Default 20. |
offset |
Number of previous items to offset the returned page by. Optional, use in conjunction with limit . |
Response
Returns a paged list of bookmarks. Each will contain a list of tags
related
to this bookmark. The meta
field contains metadata related to the type of bookmark. In the example, a GitHub starred
repo that’s been imported, the metadata contains recent commit and release information.
For many bookmarks meta
will just be null
.
Add a bookmark
curl "https://larder.io/api/1/@me/links/add/" \
-H "Authorization: Bearer 8448136eaacd2829821e0c350e816b44e1607529" \
-X POST -d "url=https://github.com/square/picasso" -d "title=square/picasso"
-d "parent=f2ac2637-aa4b-4b03-be56-224d3008742a" -d "tags=java"
import requests
import json
bookmark = {
'parent':'f2ac2637-aa4b-4b03-be56-224d3008742a',
'title': 'square/picasso',
'url': 'https://github.com/square/picasso',
'tags': ['java']
}
response = requests.post("https://larder.io/api/1/@me/links/add/",
data=json.dumps(bookmark),
headers={'Authorization':'Bearer 1ffb9306d260981da0a2a1057cebdcad'})
Returns JSON bookmark object on success:
{
"id": "2fac2637-a4ab-4b03-be56-224d3608742f",
"parent": {
"id": "f2ac2637-aa4b-4b03-be56-224d3008742a",
"name": "Android",
"color": "cb8fa0",
"created": "2016-02-12T10:40:00Z",
"modified": "2016-05-14T07:53:48Z"
},
"tags": [
{
"id": "c1e3ced3-2702-4b49-a771-2d070d3b1c0e",
"name": "java",
"color": "8fcb00",
"created": "2016-02-12T11:41:41Z",
"modified": "2016-05-14T07:53:48Z"
}
],
"title": "square/picasso",
"description": null,
"url": "https://github.com/square/picasso",
"domain": "github.com",
"created": "2016-12-01T12:44:18Z",
"modified": "2016-12-01T12:44:18Z",
"meta": null
}
Arguably the most important endpoint in the entire API.
HTTP Request
POST https://larder.io/api/1/@me/links/add/
Parameters
Name | Description |
---|---|
title |
Optional string (max 120 chars) representing the page’s title. Defaults to the URL field if left empty. |
url |
String of the URL you’d like to save. |
parent |
UUID (the id field) of the folder that’ll hold this bookmark. |
tags |
Required (but can be empty) array of tag names, eg. tags: ["json","python"] . If any don’t exist they will be created. |
description |
Optional string (max 160 chars) where you might add a little note about the bookmark. |
If you use application/x-www-form-urlencoded
parameters you can build up tags by adding the key multiple times,
eg. tags=python&tags=web
. In JSON just send these as an array.
Response
Returns a HTTP 201 status and the saved JSON bookmark object if all went well. Otherwise
you’ll get a HTTP 400 status and a JSON object with an error
field detailing the error. You may also get a 404
if the parent ID is incorrect.
If your user’s trial has ended, you’ll receive a HTTP 402 status code with the response: {"error": "Trial ended: cannot add new bookmarks."}
Edit a bookmark
curl "https://larder.io/api/1/@me/links/2fac2637-a4ab-4b03-be56-224d3608742f/edit/" \
-H "Authorization: Bearer 8448136eaacd2829821e0c350e816b44e1607529" \
-X POST -d "url=https://github.com/square/picasso" -d "title=square/picasso"
import requests
response = requests.post("https://larder.io/api/1/@me/links/%s/edit/" % bookmark_id,
data={'parent':'f2ac2637-aa4b-4b03-be56-224d3008742a'},
headers={'Authorization':'Bearer 1ffb9306d260981da0a2a1057cebdcad'})
Returns JSON bookmark object on success:
{
"id": "2fac2637-a4ab-4b03-be56-224d3608742f",
"parent": "f2ac2637-aa4b-4b03-be56-224d3008742a",
"tags": [
{
"id": "c1e3ced3-2702-4b49-a771-2d070d3b1c0e",
"name": "java",
"color": "8fcb00",
"created": "2016-02-12T11:41:41Z",
"modified": "2016-05-14T07:53:48Z"
}
],
"title": "square/picasso",
"description": null,
"url": "https://github.com/square/picasso",
"domain": "github.com",
"created": "2016-12-01T12:44:18Z",
"modified": "2016-12-01T12:44:18Z",
"meta": null
}
Update a bookmark’s fields.
HTTP Request
POST https://larder.io/api/1/@me/links/:id/edit/
Parameters
You may send only the fields you wish to update. Any fields not sent will remain unchanged.
Name | Description |
---|---|
title |
Optional string (max 120 chars) representing the page’s title. Defaults to the URL field if left empty. |
url |
String of the URL you’d like to save. |
parent |
UUID (the id field) of the folder that’ll hold this bookmark. |
description |
Optional string (max 160 chars) where you might add a little note about the bookmark. |
tags |
Optional array of tag names, eg. tags: ["json","python"] . If any don’t exist they will be created. |
If you use application/x-www-form-urlencoded
parameters you can build up tags by adding the key multiple times,
eg. tags=python&tags=web
. In JSON just send these as an array. An empty array will clear existing tags.
Response
Returns a HTTP 200 status and the saved JSON bookmark object if all went well. Otherwise you’ll get a HTTP 400 status and a JSON array or object of errors. You may also get a 404 if the parent ID is incorrect.
If your user’s trial has ended, you’ll receive a HTTP 402 status code with the response: {"error": "Trial ended: cannot add new bookmarks."}
Delete a bookmark
curl "https://larder.io/api/1/@me/links/2fac2637-a4ab-4b03-be56-224d3608742f/delete/" \
-X POST -H "Authorization: Bearer 8448136eaacd2829821e0c350e816b44e1607529"
Delete a bookmark permanently.
HTTP Request
POST https://larder.io/api/1/@me/links/:id/delete/
Response
Returns a HTTP 204 status and no content if all went well. Otherwise you’ll get a HTTP 404 status if it couldn’t be found.
Tags
Get all tags
import requests
requests.get("https://larder.io/api/1/@me/tags/",
headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
curl "https://larder.io/api/1/@me/tags/" -H "Authorization: Bearer xyz"
Returns JSON:
{
"count": 260,
"next": "https://larder.io/api/1/@me/tags/?limit=20&offset=20",
"previous": null,
"results": [
{
"id": "9252e320-cc28-415f-b1f6-be28a90d07ed",
"name": "algorithm",
"color": "00bfcb",
"created": "2016-02-25T06:39:09Z",
"modified": "2016-05-14T07:53:51Z"
},
{
"id": "2c1bc264-e557-42be-a83b-2bfe53f1cd9c",
"name": "analysis",
"color": "8c5b6c",
"created": "2016-02-25T06:39:08Z",
"modified": "2016-05-14T07:53:50Z"
}
]
}
Requesting a specific folder returns a paged list of the folder’s bookmark contents.
HTTP Request
GET https://larder.io/api/1/@me/tags/
Parameters
Name | Description |
---|---|
limit |
Optional number of tags to return per page. Default 20. |
offset |
Number of previous items to offset the returned page by. Optional, use in conjunction with limit . |
Response
Returns a paged list of tags.
Add a tag
curl "https://larder.io/api/1/@me/tags/add/" \
-X POST -d "name=algorithm" \
-H "Authorization: Bearer 8448136eaacd2829821e0c350e816b44e1607529"
import requests
response = requests.post("https://larder.io/api/1/@me/tags/add/",
data={'name':'algorithm'},
headers={'Authorization':'Bearer 1ffb9306d260981da0a2a1057cebdcad'})
Returns the JSON tag object if successful:
{
"id": "9252e320-cc28-415f-b1f6-be28a90d07ed",
"name": "algorithm",
"color": "00bfcb",
"created": "2016-02-25T06:39:09Z",
"modified": "2016-02-25T06:39:09Z"
}
Creates a new tag object you may later “tag” some good bookmarks with.
HTTP Request
POST https://larder.io/api/1/@me/tags/add/
Parameters
Name | Description |
---|---|
name |
String (max 64 chars) representing the tag’s name. This must be a 'slug’ field meaning only alphanumeric characters, underscores, and hyphens. |
Response
Returns a HTTP 201 status and the saved JSON tag object if all went well. Otherwise you’ll get a HTTP 400 status and a JSON object with fields detailing any errors.
Edit a tag
import requests
response = requests.post("https://larder.io/api/1/@me/tags/%s/edit/" % tag_id,
data={'name':'algorithms'},
headers={'Authorization':'Bearer 1ffb9306d260981da0a2a1057cebdcad'})
curl "https://larder.io/api/1/@me/tags/9252e320-cc28-415f-b1f6-be28a90d07ed/edit/" \
-H "Authorization: Bearer 8448136eaacd2829821e0c350e816b44e1607529" \
-X POST -d "name=algorithms"
Returns JSON tag object if successful:
{
"id": "9252e320-cc28-415f-b1f6-be28a90d07ed",
"name": "algorithms",
"color": "00bfcb",
"created": "2016-02-25T06:39:09Z",
"modified": "2016-05-14T07:53:51Z"
}
Rename a tag you previously put a typo in.
HTTP Request
POST https://larder.io/api/1/@me/tags/:id/edit/
Parameters
Name | Description |
---|---|
name |
String (max 64 chars) representing the tag’s name. This must be a 'slug’ field meaning only alphanumeric characters, underscores, and hyphens. |
Response
Returns a HTTP 200 status and the saved JSON tag object if all went well. Otherwise you’ll get a HTTP 400 status and a JSON object with fields detailing any errors.
Delete a tag
curl "https://larder.io/api/1/@me/tags/2faca637-a4ab-4b03-be56-2a4d3608742f/delete/" \
-X POST -H "Authorization: Bearer 1ffb9306d260981da0a2a1057cebdcad"
import requests
response = requests.post("https://larder.io/api/1/@me/tags/%s/delete/" % tag_id,
headers={'Authorization':'Bearer 1ffb9306d260981da0a2a1057cebdcad'})
Delete a tag permanently. Any bookmarks with this tag attached just get that tag removed.
HTTP Request
POST https://larder.io/api/1/@me/tags/:id/delete/
Response
Returns a HTTP 204 status and no content if all went well. Otherwise you’ll get a HTTP 404 status if it couldn’t be found.
Searching
You can find bookmarks by searching with a query. This query string will be matched against bookmark titles, URLs, descriptions, and tags.
curl "https://larder.io/api/1/@me/search/?q=python" \
-H "Authorization: Bearer 1ffb9306d260981da0a2a1057cebdcad"
import requests
response = requests.get("https://larder.io/api/1/@me/search/",
params={'q':'python'},
headers={'Authorization':'Bearer 1ffb9306d260981da0a2a1057cebdcad'})
print(response.json())
Returns a paged set of JSON results:
{
"count": 217,
"next": "http://larder.athena.lan/api/1/@me/search/?limit=20&offset=20&q=python",
"previous": null,
"results": [
{
"id": "a53f271a-c3b0-4d49-b6f1-9b532b3b0dcc",
"parent": {
"id": "42d1a97e-8e5b-420f-b50c-a49a69cb183a",
"name": "Coding",
"color": "289ef0",
"icon": "",
"created": "2016-01-10T03:56:16Z",
"modified": "2017-02-11T12:02:07Z",
"parent": null
},
"tags": [
{
"id": "f98c7bd5-747d-4cd7-9b16-300f26ef67f6",
"name": "python",
"color": "0099cb",
"created": "2016-01-10T03:57:21Z",
"modified": "2016-05-14T07:53:46Z"
}
],
"title": "kennethreitz/requests",
"description": "Python HTTP Requests for Humans™",
"url": "https://github.com/kennethreitz/requests",
"domain": "github.com",
"created": "2016-12-01T05:32:57Z",
"modified": "2016-12-01T05:38:44Z",
"meta": {
"commit": {
"author": "Lukasa",
"date": "2016-11-30T12:38:26Z",
"message": "v2.12.2"
},
"type": "github.com"
}
}
]
}
HTTP Request
GET https://larder.io/api/1/@me/search/
URL Parameters
Parameter | Description |
---|---|
q | The string to search for. This may start with # to find results tagged with exactly this tag, for example ?q=#python returns only bookmarks with the tag python . |