{"id":3485,"date":"2016-03-22T02:43:53","date_gmt":"2016-03-22T02:43:53","guid":{"rendered":"http:\/\/www.garysieling.com\/blog\/?p=3485"},"modified":"2016-03-22T02:43:53","modified_gmt":"2016-03-22T02:43:53","slug":"simple-pinboard-api-example","status":"publish","type":"post","link":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/","title":{"rendered":"A simple Pinboard API example"},"content":{"rendered":"<p>I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc).<\/p>\n<p>The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like so:<\/p>\n<pre lang=\"javascript\">\nget_posts(pinboard_api, 'recent', pinboard_user_data)\n  .then( \n    (data) => {\n      async.map(data, do_something)\n    }\n  ).error(\n    err\n  )\n<\/pre>\n<p>This ended up being a pretty fascinating trip down the vast list of node modules, and I ended up with this:<\/p>\n<pre lang=\"javascript\">\nlet _ = require('lodash'), \n    request = require('request'),\n    Promise = require('bluebird'),\n    rp = require('request-promise'),\n    xml2js = require('xml2js'),\n    async = require('async');\n<\/pre>\n<p><b>lodash<\/b> is a nice functional programming library &#8211; great utility belt API for working on collections<br \/>\n<b>bluebird<\/b> is a promise library. I don&#8217;t really like promises, but they do let you flatten some of the nesting that shows up in Node callbacks. There is another library, &#8220;Q&#8221;, that seems to be more popular, but Bluebird promises better error handling.<br \/>\n<b>request<\/b> seems to be the most popular way to do curl\/ajax\/wget style calls<br \/>\n<b>request-promise<\/b> lets you use HTTP request results as promises<br \/>\n<b>xml2js<\/b> lets you parse XML text into JS objects (Pinboard returns XML)<br \/>\n<b>async<\/b> is a library like lodash, but lets you turn the &#8220;map&#8221; type functions into parallel loops<\/p>\n<p>Next, I&#8217;ve defined some structure about the API, thinking I might want to support other APIs. Here are the important facts about this one:<\/p>\n<pre>\nlet pinboard_api = {\n  'auth': 'auth_token',\n  'recent': {\n    returns: [\"href\",\"time\",\"description\",\"extended\",\"tag\",\"hash\",\"shared\"],\n    url: 'https:\/\/api.pinboard.in\/v1\/posts\/recent',\n    unique_key: 'hash',\n    implicit_keys: {\n      count: 100\n    },\n    iter: (result) => result.posts.post  \n  }\n}\n<\/pre>\n<p>Authentication is usually the trickiest bit with these integrations (aside from SSL). Pinboard just takes a single authentication token, on your account:<\/p>\n<pre lang=\"javascript\">\nlet pinboard_user_data = {\n  'auth_token': '-------------',\n}\n<\/pre>\n<p>Now, to actually do the work we can combine all this together. Conceptually it&#8217;s really simple but complexified a bit by my attempts to leave room to support asynchronous operations + other endpoints and APIs (note I haven&#8217;t implemented hiding things that have been seen recently &#8211; imagine that is a lookup elsewhere).<\/p>\n<pre lang=\"javascript\">\nfunction post_fields(api, method, user_data) {\n  let qs = {};\n  qs[api.auth] = user_data[api.auth];  \n  _.extend(qs, api[method].implicit_keys);\n  \n  let options = {\n    uri: api[method].url,\n    qs: qs,\n    headers: {\n      'User-Agent': 'Request-Promise'\n    } \n  };\n      \n  return new Promise(\n    (resolve, reject) => {\n      rp(options)\n        .then(\n          (resp) => {\n            xml2js.parseString(resp, \n              (err, result) => {\n                if (!result) {\n                  reject(err); \n                }                  \n                \n                resolve(\n                  _(api[method].iter(result))\n                    .filter(\n                      (entry) => !seen(entry)\n                    )\n                    .map(\n                      (entry) => {\n                        markSeen(entry.$)\n                        \n                        return entry.$;\n                      }  \n                    ).value()\n                )\n              });\n          }\n        )\n        .error(reject)    \n    }    \n  );\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;A simple Pinboard API example&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"footnotes":""},"categories":[4],"tags":[288,302,434],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 4.9.9 - aioseo.com -->\n\t<meta name=\"description\" content=\"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"gary\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 4.9.9\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Gary Sieling - Software Engineer\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"A simple Pinboard API example - Gary Sieling\" \/>\n\t\t<meta property=\"og:description\" content=\"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2016-03-22T02:43:53+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2016-03-22T02:43:53+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"A simple Pinboard API example - Gary Sieling\" \/>\n\t\t<meta name=\"twitter:description\" content=\"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#blogposting\",\"name\":\"A simple Pinboard API example - Gary Sieling\",\"headline\":\"A simple Pinboard API example\",\"author\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/author\\\/gary\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/#organization\"},\"datePublished\":\"2016-03-22T02:43:53+00:00\",\"dateModified\":\"2016-03-22T02:43:53+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#webpage\"},\"articleSection\":\"Code Examples, integrations, javascript, pinboard\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.garysieling.com\\\/blog\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/category\\\/code-examples\\\/#listItem\",\"name\":\"Code Examples\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/category\\\/code-examples\\\/#listItem\",\"position\":2,\"name\":\"Code Examples\",\"item\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/category\\\/code-examples\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#listItem\",\"name\":\"A simple Pinboard API example\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#listItem\",\"position\":3,\"name\":\"A simple Pinboard API example\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/category\\\/code-examples\\\/#listItem\",\"name\":\"Code Examples\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/#organization\",\"name\":\"Gary Sieling\",\"description\":\"Software Engineer\",\"url\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/author\\\/gary\\\/#author\",\"url\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/author\\\/gary\\\/\",\"name\":\"gary\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0be925276d848ffe98a6a9dc8cf33e67?s=96&d=identicon&r=g\",\"width\":96,\"height\":96,\"caption\":\"gary\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#webpage\",\"url\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/\",\"name\":\"A simple Pinboard API example - Gary Sieling\",\"description\":\"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/simple-pinboard-api-example\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/author\\\/gary\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/author\\\/gary\\\/#author\"},\"datePublished\":\"2016-03-22T02:43:53+00:00\",\"dateModified\":\"2016-03-22T02:43:53+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/\",\"name\":\"Gary Sieling\",\"description\":\"Software Engineer\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.garysieling.com\\\/blog\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"A simple Pinboard API example - Gary Sieling","description":"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like","canonical_url":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#blogposting","name":"A simple Pinboard API example - Gary Sieling","headline":"A simple Pinboard API example","author":{"@id":"https:\/\/www.garysieling.com\/blog\/author\/gary\/#author"},"publisher":{"@id":"https:\/\/www.garysieling.com\/blog\/#organization"},"datePublished":"2016-03-22T02:43:53+00:00","dateModified":"2016-03-22T02:43:53+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#webpage"},"isPartOf":{"@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#webpage"},"articleSection":"Code Examples, integrations, javascript, pinboard"},{"@type":"BreadcrumbList","@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/www.garysieling.com\/blog#listItem","position":1,"name":"Home","item":"https:\/\/www.garysieling.com\/blog","nextItem":{"@type":"ListItem","@id":"https:\/\/www.garysieling.com\/blog\/category\/code-examples\/#listItem","name":"Code Examples"}},{"@type":"ListItem","@id":"https:\/\/www.garysieling.com\/blog\/category\/code-examples\/#listItem","position":2,"name":"Code Examples","item":"https:\/\/www.garysieling.com\/blog\/category\/code-examples\/","nextItem":{"@type":"ListItem","@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#listItem","name":"A simple Pinboard API example"},"previousItem":{"@type":"ListItem","@id":"https:\/\/www.garysieling.com\/blog#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#listItem","position":3,"name":"A simple Pinboard API example","previousItem":{"@type":"ListItem","@id":"https:\/\/www.garysieling.com\/blog\/category\/code-examples\/#listItem","name":"Code Examples"}}]},{"@type":"Organization","@id":"https:\/\/www.garysieling.com\/blog\/#organization","name":"Gary Sieling","description":"Software Engineer","url":"https:\/\/www.garysieling.com\/blog\/"},{"@type":"Person","@id":"https:\/\/www.garysieling.com\/blog\/author\/gary\/#author","url":"https:\/\/www.garysieling.com\/blog\/author\/gary\/","name":"gary","image":{"@type":"ImageObject","@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/0be925276d848ffe98a6a9dc8cf33e67?s=96&d=identicon&r=g","width":96,"height":96,"caption":"gary"}},{"@type":"WebPage","@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#webpage","url":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/","name":"A simple Pinboard API example - Gary Sieling","description":"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/www.garysieling.com\/blog\/#website"},"breadcrumb":{"@id":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/#breadcrumblist"},"author":{"@id":"https:\/\/www.garysieling.com\/blog\/author\/gary\/#author"},"creator":{"@id":"https:\/\/www.garysieling.com\/blog\/author\/gary\/#author"},"datePublished":"2016-03-22T02:43:53+00:00","dateModified":"2016-03-22T02:43:53+00:00"},{"@type":"WebSite","@id":"https:\/\/www.garysieling.com\/blog\/#website","url":"https:\/\/www.garysieling.com\/blog\/","name":"Gary Sieling","description":"Software Engineer","inLanguage":"en-US","publisher":{"@id":"https:\/\/www.garysieling.com\/blog\/#organization"}}]},"og:locale":"en_US","og:site_name":"Gary Sieling - Software Engineer","og:type":"article","og:title":"A simple Pinboard API example - Gary Sieling","og:description":"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like","og:url":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/","article:published_time":"2016-03-22T02:43:53+00:00","article:modified_time":"2016-03-22T02:43:53+00:00","twitter:card":"summary_large_image","twitter:title":"A simple Pinboard API example - Gary Sieling","twitter:description":"I wrote up an example using the Pinboard API from Node: this is written with the idea that I might combine it with other external services (dropbox, facebook, etc). The ideal situation here is to iterate over recently bookmarked posts, then call some function on each unseen result (external API, log to screen, etc), like"},"aioseo_meta_data":{"post_id":"3485","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"limit_modified_date":false,"created":"2023-02-04 16:44:34","updated":"2026-07-06 01:40:39","ai":null,"breadcrumb_settings":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.garysieling.com\/blog\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.garysieling.com\/blog\/category\/code-examples\/\" title=\"Code Examples\">Code Examples<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tA simple Pinboard API example\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/www.garysieling.com\/blog"},{"label":"Code Examples","link":"https:\/\/www.garysieling.com\/blog\/category\/code-examples\/"},{"label":"A simple Pinboard API example","link":"https:\/\/www.garysieling.com\/blog\/simple-pinboard-api-example\/"}],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts\/3485"}],"collection":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/comments?post=3485"}],"version-history":[{"count":0,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts\/3485\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/media?parent=3485"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/categories?post=3485"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/tags?post=3485"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}