CINXE.COM
api | code.flickr.com
<!DOCTYPE html> <!--[if IE 6]> <html id="ie6" lang="en-US"> <![endif]--> <!--[if IE 7]> <html id="ie7" lang="en-US"> <![endif]--> <!--[if IE 8]> <html id="ie8" lang="en-US"> <![endif]--> <!--[if !(IE 6) & !(IE 7) & !(IE 8)]><!--> <html lang="en-US"> <!--<![endif]--> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <title> api | code.flickr.com </title> <link rel="profile" href="https://gmpg.org/xfn/11" /> <link rel="stylesheet" type="text/css" media="all" href="https://code.flickr.net/wp-content/themes/flickr-code/style.css?ver=20240716" /> <link rel="pingback" href="https://code.flickr.net/xmlrpc.php"> <!--[if lt IE 9]> <script src="https://code.flickr.net/wp-content/themes/twentyeleven/js/html5.js?ver=3.7.0" type="text/javascript"></script> <![endif]--> <meta name='robots' content='max-image-preview:large' /> <style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style> <link rel='dns-prefetch' href='//stats.wp.com' /> <link rel="alternate" type="application/rss+xml" title="code.flickr.com » Feed" href="https://code.flickr.net/feed/" /> <link rel="alternate" type="application/rss+xml" title="code.flickr.com » Comments Feed" href="https://code.flickr.net/comments/feed/" /> <link rel="alternate" type="application/rss+xml" title="code.flickr.com » api Tag Feed" href="https://code.flickr.net/tag/api/feed/" /> <script type="text/javascript"> /* <![CDATA[ */ window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/svg\/","svgExt":".svg","source":{"concatemoji":"https:\/\/code.flickr.net\/wp-includes\/js\/wp-emoji-release.min.js?ver=6.7.2"}}; /*! This file is auto-generated */ !function(i,n){var o,s,e;function c(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function p(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data),r=(e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0),new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data));return t.every(function(e,t){return e===r[t]})}function u(e,t,n){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\uddfa\ud83c\uddf3","\ud83c\uddfa\u200b\ud83c\uddf3")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!n(e,"\ud83d\udc26\u200d\u2b1b","\ud83d\udc26\u200b\u2b1b")}return!1}function f(e,t,n){var r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):i.createElement("canvas"),a=r.getContext("2d",{willReadFrequently:!0}),o=(a.textBaseline="top",a.font="600 32px Arial",{});return e.forEach(function(e){o[e]=t(a,e,n)}),o}function t(e){var t=i.createElement("script");t.src=e,t.defer=!0,i.head.appendChild(t)}"undefined"!=typeof Promise&&(o="wpEmojiSettingsSupports",s=["flag","emoji"],n.supports={everything:!0,everythingExceptFlag:!0},e=new Promise(function(e){i.addEventListener("DOMContentLoaded",e,{once:!0})}),new Promise(function(t){var n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),p.toString()].join(",")+"));",r=new Blob([e],{type:"text/javascript"}),a=new Worker(URL.createObjectURL(r),{name:"wpTestEmojiSupports"});return void(a.onmessage=function(e){c(n=e.data),a.terminate(),t(n)})}catch(e){}c(n=f(s,u,p))}t(n)}).then(function(e){for(var t in e)n.supports[t]=e[t],n.supports.everything=n.supports.everything&&n.supports[t],"flag"!==t&&(n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&n.supports[t]);n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&!n.supports.flag,n.DOMReady=!1,n.readyCallback=function(){n.DOMReady=!0}}).then(function(){return e}).then(function(){var e;n.supports.everything||(n.readyCallback(),(e=n.source||{}).concatemoji?t(e.concatemoji):e.wpemoji&&e.twemoji&&(t(e.twemoji),t(e.wpemoji)))}))}((window,document),window._wpemojiSettings); /* ]]> */ </script> <style id='wp-emoji-styles-inline-css'> img.wp-smiley, img.emoji { display: inline !important; border: none !important; box-shadow: none !important; height: 1em !important; width: 1em !important; margin: 0 0.07em !important; vertical-align: -0.1em !important; background: none !important; padding: 0 !important; } </style> <link rel='stylesheet' id='all-css-2' href='https://code.flickr.net/wp-includes/css/dist/block-library/style.min.css?m=1742994400g' type='text/css' media='all' /> <style id='wp-block-library-theme-inline-css'> .wp-block-audio :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-audio :where(figcaption){color:#ffffffa6}.wp-block-audio{margin:0 0 1em}.wp-block-code{border:1px solid #ccc;border-radius:4px;font-family:Menlo,Consolas,monaco,monospace;padding:.8em 1em}.wp-block-embed :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-embed :where(figcaption){color:#ffffffa6}.wp-block-embed{margin:0 0 1em}.blocks-gallery-caption{color:#555;font-size:13px;text-align:center}.is-dark-theme .blocks-gallery-caption{color:#ffffffa6}:root :where(.wp-block-image figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme :root :where(.wp-block-image figcaption){color:#ffffffa6}.wp-block-image{margin:0 0 1em}.wp-block-pullquote{border-bottom:4px solid;border-top:4px solid;color:currentColor;margin-bottom:1.75em}.wp-block-pullquote cite,.wp-block-pullquote footer,.wp-block-pullquote__citation{color:currentColor;font-size:.8125em;font-style:normal;text-transform:uppercase}.wp-block-quote{border-left:.25em solid;margin:0 0 1.75em;padding-left:1em}.wp-block-quote cite,.wp-block-quote footer{color:currentColor;font-size:.8125em;font-style:normal;position:relative}.wp-block-quote:where(.has-text-align-right){border-left:none;border-right:.25em solid;padding-left:0;padding-right:1em}.wp-block-quote:where(.has-text-align-center){border:none;padding-left:0}.wp-block-quote.is-large,.wp-block-quote.is-style-large,.wp-block-quote:where(.is-style-plain){border:none}.wp-block-search .wp-block-search__label{font-weight:700}.wp-block-search__button{border:1px solid #ccc;padding:.375em .625em}:where(.wp-block-group.has-background){padding:1.25em 2.375em}.wp-block-separator.has-css-opacity{opacity:.4}.wp-block-separator{border:none;border-bottom:2px solid;margin-left:auto;margin-right:auto}.wp-block-separator.has-alpha-channel-opacity{opacity:1}.wp-block-separator:not(.is-style-wide):not(.is-style-dots){width:100px}.wp-block-separator.has-background:not(.is-style-dots){border-bottom:none;height:1px}.wp-block-separator.has-background:not(.is-style-wide):not(.is-style-dots){height:2px}.wp-block-table{margin:0 0 1em}.wp-block-table td,.wp-block-table th{word-break:normal}.wp-block-table :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-table :where(figcaption){color:#ffffffa6}.wp-block-video :where(figcaption){color:#555;font-size:13px;text-align:center}.is-dark-theme .wp-block-video :where(figcaption){color:#ffffffa6}.wp-block-video{margin:0 0 1em}:root :where(.wp-block-template-part.has-background){margin-bottom:0;margin-top:0;padding:1.25em 2.375em} </style> <link rel='stylesheet' id='all-css-6' href='https://code.flickr.net/_static/??-eJzTLy/QzcxLzilNSS3WzyrWz01NyUxMzUnNTc0rQeEU5CRWphbp5qSmJyZX6uVm5uklFxfr6OPTDpRD5sM02efaGpqbGFlampgYGAIAROAu5A==' type='text/css' media='all' /> <style id='jetpack-sharing-buttons-style-inline-css'> .jetpack-sharing-buttons__services-list{display:flex;flex-direction:row;flex-wrap:wrap;gap:0;list-style-type:none;margin:5px;padding:0}.jetpack-sharing-buttons__services-list.has-small-icon-size{font-size:12px}.jetpack-sharing-buttons__services-list.has-normal-icon-size{font-size:16px}.jetpack-sharing-buttons__services-list.has-large-icon-size{font-size:24px}.jetpack-sharing-buttons__services-list.has-huge-icon-size{font-size:36px}@media print{.jetpack-sharing-buttons__services-list{display:none!important}}.editor-styles-wrapper .wp-block-jetpack-sharing-buttons{gap:0;padding-inline-start:0}ul.jetpack-sharing-buttons__services-list.has-background{padding:1.25em 2.375em} </style> <style id='classic-theme-styles-inline-css'> /*! This file is auto-generated */ .wp-block-button__link{color:#fff;background-color:#32373c;border-radius:9999px;box-shadow:none;text-decoration:none;padding:calc(.667em + 2px) calc(1.333em + 2px);font-size:1.125em}.wp-block-file__button{background:#32373c;color:#fff;text-decoration:none} </style> <style id='global-styles-inline-css'> :root{--wp--preset--aspect-ratio--square: 1;--wp--preset--aspect-ratio--4-3: 4/3;--wp--preset--aspect-ratio--3-4: 3/4;--wp--preset--aspect-ratio--3-2: 3/2;--wp--preset--aspect-ratio--2-3: 2/3;--wp--preset--aspect-ratio--16-9: 16/9;--wp--preset--aspect-ratio--9-16: 9/16;--wp--preset--color--black: #000;--wp--preset--color--cyan-bluish-gray: #abb8c3;--wp--preset--color--white: #fff;--wp--preset--color--pale-pink: #f78da7;--wp--preset--color--vivid-red: #cf2e2e;--wp--preset--color--luminous-vivid-orange: #ff6900;--wp--preset--color--luminous-vivid-amber: #fcb900;--wp--preset--color--light-green-cyan: #7bdcb5;--wp--preset--color--vivid-green-cyan: #00d084;--wp--preset--color--pale-cyan-blue: #8ed1fc;--wp--preset--color--vivid-cyan-blue: #0693e3;--wp--preset--color--vivid-purple: #9b51e0;--wp--preset--color--blue: #1982d1;--wp--preset--color--dark-gray: #373737;--wp--preset--color--medium-gray: #666;--wp--preset--color--light-gray: #e2e2e2;--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--font-size--small: 13px;--wp--preset--font-size--medium: 20px;--wp--preset--font-size--large: 36px;--wp--preset--font-size--x-large: 42px;--wp--preset--spacing--20: 0.44rem;--wp--preset--spacing--30: 0.67rem;--wp--preset--spacing--40: 1rem;--wp--preset--spacing--50: 1.5rem;--wp--preset--spacing--60: 2.25rem;--wp--preset--spacing--70: 3.38rem;--wp--preset--spacing--80: 5.06rem;--wp--preset--shadow--natural: 6px 6px 9px rgba(0, 0, 0, 0.2);--wp--preset--shadow--deep: 12px 12px 50px rgba(0, 0, 0, 0.4);--wp--preset--shadow--sharp: 6px 6px 0px rgba(0, 0, 0, 0.2);--wp--preset--shadow--outlined: 6px 6px 0px -3px rgba(255, 255, 255, 1), 6px 6px rgba(0, 0, 0, 1);--wp--preset--shadow--crisp: 6px 6px 0px rgba(0, 0, 0, 1);}:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;}:where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-color{color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-color{color: var(--wp--preset--color--white) !important;}.has-pale-pink-color{color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-color{color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-color{color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-color{color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-color{color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-color{color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-color{color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-color{color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-color{color: var(--wp--preset--color--vivid-purple) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-background-color{background-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-pale-pink-background-color{background-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-background-color{background-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-background-color{background-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-background-color{background-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-background-color{background-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-background-color{background-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-background-color{background-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-background-color{background-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-background-color{background-color: var(--wp--preset--color--vivid-purple) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-border-color{border-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-pale-pink-border-color{border-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-border-color{border-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-border-color{border-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-border-color{border-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-border-color{border-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-border-color{border-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-border-color{border-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-border-color{border-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-border-color{border-color: var(--wp--preset--color--vivid-purple) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;} :where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;} :where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;} :root :where(.wp-block-pullquote){font-size: 1.5em;line-height: 1.6;} </style> <link rel='stylesheet' id='all-css-12' href='https://code.flickr.net/_static/??-eJzTLy/QTc7PK0nNK9EvyUjNTS3WLykHcipTc1LLUvP0i0sqc1L1kouLdfQxVablZCZnFwFFU1LxK0QxMiknPzm7GKTUPtfW0NzEwMTQ2MLSEABabDMX' type='text/css' media='all' /> <link rel="https://api.w.org/" href="https://code.flickr.net/wp-json/" /><link rel="alternate" title="JSON" type="application/json" href="https://code.flickr.net/wp-json/wp/v2/tags/4276" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://code.flickr.net/xmlrpc.php?rsd" /> <meta name="generator" content="WordPress 6.7.2" /> <style>img#wpstats{display:none}</style> <style type="text/css" id="twentyeleven-header-css"> #site-title, #site-description { position: absolute; clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ clip: rect(1px, 1px, 1px, 1px); } </style> <style type="text/css" id="wp-custom-css"> body { background: #fff; } body,input,textarea { font-family: Arial, sans-serif; color: #404040; } #page { margin: 1em auto; max-width: 1000px; } a:link { text-decoration: none; color: #0063dc; } a:visited { text-decoration: none; color: #0063dc; } a:hover { text-decoration: none; background: #0063dc; color: #fff; } a:active { text-decoration: none; background-color: #0259c4; color: #fff; } #branding { border: none; } #branding .only-search #s,#branding .only-search #s:focus { width: 280px; background-color: transparent; border-color: #ddd; } #branding #searchform { right: 0; } nav#access { background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none; margin: 0; } nav#access a { font-weight: bold; margin: 11px 28px 0 0; padding: 0; line-height: 21px; } nav#access a:link,nav#access a:visited { text-decoration: none; color: #888; } nav#access a:hover,nav#access li:hover > a,nav#access a:focus,nav#access a:active { text-decoration: none; color: #0063dc; background: none; } #branding .only-search + nav#access div { padding-right: 300px; } nav#access div,nav#access ul { margin: 0; } @media screen and (max-width: 768px) { #branding .only-search + nav#access div { padding-right: 0; } #branding #searchform { display: none; position: static; text-align: center; } #branding .with-image #searchform { max-width: 100%; } #branding .only-search #s,#branding .only-search #s:focus { width: 85%; float: none; } nav#access { margin: 0 0 0 5%; } } @media screen and (max-width: 480px) { nav#access { display: none; } } #content { margin: 0 20% 0 0; width: 80%; } #content .comments-link { display: none; } @media screen and (max-width: 800px) { #main { padding-top: 0; overflow: hidden; } #branding { padding-bottom: 0; } #main #content { margin: 0; } .entry-title,.entry-header .entry-meta { padding-right: 0; } } .singular #content,.left-sidebar.singular #content { margin: 0 10%; width: 80%; } .singular .entry-header,.singular .entry-content,.singular footer.entry-meta,.singular #comments-title { width: 100%; } .hentry,.no-results { border-bottom: 1px dotted #dadada; } .singular .hentry { padding-top: 1em; } .entry-title { padding-bottom: 0; } .entry-title,.entry-title a { font-size: 28px; color: #000; } .entry-title a:hover,.entry-title a:focus,.entry-title a:active { color: #0063dc; background-color: transparent; } @media screen and (max-width: 650px) { .singular .entry-title { padding-top: 0; line-height: 42px; } } .singular .entry-content { margin-top: 0; } #jp-post-flair { margin-top: 3em; } footer.entry-meta .cat-links,footer.entry-meta .sep,footer.entry-meta .tag-links { display: none; } .singular .entry-meta .edit-link a { position: static; } .singular footer.entry-meta { margin-top: 2em; } #comments { display: none; } #site-generator { background: transparent; border-top: none; padding: 0 .5em; } img#wpstats { position: absolute !important; clip: rect(1px 1px 1px 1px); } #secondary { margin-right: 0; width: 16%; text-align: right; } #secondary #s { display: none; } #secondary aside ul { list-style: none; } #secondary .widget-title { color: #999; font-size: 12px; letter-spacing: auto; text-transform: none; } .widget a { font-weight: normal; } .widget a:hover,.widget a:focus,.widget a:active { text-decoration: none; } #everything-after-this-line-is-a-post-level-style---yay-for-wordpress-stripping-comments-from-css { color: #000; } .entry-content p { margin-bottom: 1.3em; } .entry-content p.note { padding: 11px; background-color: #fffdeb; border-bottom: 1px solid #fff9c2; } .entry-content p.warning { padding: 11px; background-color: #fdf8f8; border-bottom: 1px solid #f7dedd; } .entry-content .aside { margin-bottom: 1.3em; padding: 11px; background-color: #f8fdf8; border-bottom: 1px solid #def6df; } .entry-content .aside p:last-child { margin-bottom: 0; } .entry-content h2,.entry-content h3,.entry-content h4 { font-weight: bold; color: #000; text-transform: none; } .entry-content h2 { font-size: 22px; } .entry-content h3 { font-size: 16px; letter-spacing: 0; line-height: 1.3em; margin-bottom: 1em; } .entry-content h4 { font-size: 13px; line-height: 1.4em; margin-bottom: 0; } .entry-content p.flickr-photo,.entry-content p.flickr-photo a,.entry-content p.figure { color: #999; font-size: 14px; } .entry-content p.flickr-photo a:hover,.entry-content p.flickr-photo a:active { color: #0063dc; background-color: transparent; } .entry-content p.flickr-photo .caption { display: block; padding-left: 22px; background: url('http://farm4.staticflickr.com/3329/favicons/72157601614001242_7730.png') no-repeat 0 3px; } .entry-content img { max-width: 100%; height: auto; } .undersized-image-container { text-align: center; } .entry-content code { color: #000; font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; font-size: 14px; background-color: #F6FcFF; border-bottom: 1px solid #ebf5ff; } .entry-content ul { list-style: disc; } .entry-content ol ol { list-style: lower-alpha; } .entry-content table caption { font-size: 13px; font-weight: bold; color: #000; } .entry-content table tr.odd td,.entry-content table tr.odd th { background-color: #f8f8f8; } .entry-content table td,.entry-content table th { padding: 3px 10px; line-height: 1.4em; } .entry-content table tr th { color: #000; font-weight: bold; text-transform: none; font-size: 13px; letter-spacing: 0; border-top: 1px solid #ddd; } .entry-content table tr td { font-size: 14px; } .entry-content table.data tr td,.entry-content table.data thead tr th { text-align: right; } .entry-content table.tight td,.entry-content table.tight th { padding: 3px 6px; } .entry-content table.tight th { font-size: 12px; } .entry-content table.tight td { font-size: 13px; } @media screen and (max-width: 650px) { .entry-content table td,.entry-content table th { padding: 3px 5px; } .entry-content table tr th { font-size: 12px; } .entry-content table tr td { font-size: 13px; } .entry-content table.tight td,.entry-content table.tight th { padding: 3px 4px; } .entry-content table.tight th { font-size: 11px; } .entry-content table.tight td { font-size: 12px; } } .entry-content blockquote { font-family: inherit; font-style: normal; font-weight: normal; margin: 0 2em 1.3em; padding: 1em; background: #F9F9F9; border-bottom: 1px solid #ececec; } .entry-content blockquote p.source { margin-bottom: 0; } .entry-content .hiring-banner { position: relative; background: #ffebf5; padding: 11px 11px 11px 88px; border-bottom: 1px solid #ffc5e2; margin: 2em 0; } .entry-content .hiring-banner p { margin-bottom: 0; } .entry-content .hiring-banner .group-photo { position: absolute; left: -57px; top: -5px; width: 120px; padding: 6px; background: #fff; -webkit-box-shadow: 1px 1px 8px rgba(50,50,50,0.8); -moz-box-shadow: 1px 1px 8px rgba(50,50,50,0.8); box-shadow: 1px 1px 8px rgba(50,50,50,0.8); -webkit-transform: rotate(-11deg); -moz-transform: rotate(-11deg); -o-transform: rotate(-11deg); -ms-transform: rotate(-11deg); } .entry-content .hiring-banner .group-photo img { display: block; margin: 0; max-width: 100%; } @media screen and (max-width: 800px) { .entry-content .hiring-banner { padding-left: 11px; } .entry-content .hiring-banner .group-photo { display: none; } } #everything-after-this-line-is-for-the-syntaxhighlighter---all-rules-require-important { color: #000; } #main .syntaxhighlighter .lines { border-bottom: 1px solid #ebf5ff !important; } #main .syntaxhighlighter,#main .syntaxhighlighter div,#main .syntaxhighlighter code,#main .syntaxhighlighter table,#main .syntaxhighlighter table td,#main .syntaxhighlighter table tr,#main .syntaxhighlighter table tbody { font-size: 14px !important; } #main .syntaxhighlighter .line.alt1,#main .syntaxhighlighter .line.alt2 { background-color: #fafdff !important; } #main .syntaxhighlighter .line.highlighted { background-color: #fffbd6 !important; } #main .syntaxhighlighter .plain,#main .syntaxhighlighter .plain a { color: #000 !important; } #main .syntaxhighlighter .comments,#main .syntaxhighlighter .comments a { color: #999 !important; } #main .syntaxhighlighter .string,#main .syntaxhighlighter .string a { color: #ff52a9 !important; } #main .syntaxhighlighter .keyword { color: #0034fe !important; font-weight: normal !important; } #main .syntaxhighlighter .preprocessor { color: #417ba9 !important; } #main .syntaxhighlighter .variable { color: #b130c0 !important; } #main .syntaxhighlighter .value { color: #6b77f7 !important; } #main .syntaxhighlighter .functions { color: #002ad5 !important; } #main .syntaxhighlighter .constants { color: #d11e08 !important; } #main .syntaxhighlighter .script { background-color: yellow !important; } #main .syntaxhighlighter .color1,#main .syntaxhighlighter .color1 a { color: #808080 !important; } #main .syntaxhighlighter .color2,#main .syntaxhighlighter .color2 a { color: #ff1493 !important; } #main .syntaxhighlighter .color3,#main .syntaxhighlighter .color3 a { color: red !important; } a[data-flickr-embed] img { width: 800px; } </style> </head> <body class="archive tag tag-api tag-4276 custom-background wp-embed-responsive jps-theme-flickr-code two-column right-sidebar"> <div class="skip-link"><a class="assistive-text" href="#content">Skip to primary content</a></div><div class="skip-link"><a class="assistive-text" href="#secondary">Skip to secondary content</a></div><div id="page" class="hfeed"> <header id="branding"> <hgroup> <h1 id="site-title"><span><a href="https://code.flickr.net/" rel="home">code.flickr.com</a></span></h1> <h2 id="site-description"></h2> </hgroup> <a href="https://code.flickr.net/"> <img src="https://wp.flickr.net/wp-content/uploads/sites/3/2012/09/code-flickr-com-drawn-header-grey-large.png" width="1000" height="157" alt="code.flickr.com" decoding="async" fetchpriority="high" /> </a> <div class="only-search with-image"> <form method="get" id="searchform" action="https://code.flickr.net/"> <label for="s" class="assistive-text">Search</label> <input type="text" class="field" name="s" id="s" placeholder="Search" /> <input type="submit" class="submit" name="submit" id="searchsubmit" value="Search" /> </form> </div> <nav id="access"> <h3 class="assistive-text">Main menu</h3> <div class="menu-menu-container"><ul id="menu-menu" class="menu"><li id="menu-item-2084" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2084"><a href="http://www.flickr.com/">Flickr</a></li> <li id="menu-item-2085" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2085"><a href="http://blog.flickr.net/">Flickr Blog</a></li> <li id="menu-item-2250" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2250"><a href="http://twitter.com/flickr">@flickr</a></li> <li id="menu-item-2086" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2086"><a href="http://twitter.com/flickrapi">@flickrapi</a></li> <li id="menu-item-2087" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2087"><a href="https://www.flickr.com/services/developer/">Developer Guidelines</a></li> <li id="menu-item-2088" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2088"><a href="http://www.flickr.com/services/api/">API</a></li> <li id="menu-item-2089" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-2089"><a href="http://www.flickr.com/jobs/">Jobs</a></li> </ul></div> </nav><!-- #access --> </header><!-- #branding --> <div id="main"> <section id="primary"> <div id="content" role="main"> <header class="page-header"> <h1 class="page-title"> Tag Archives: <span>api</span> </h1> </header> <nav id="nav-above"> <h3 class="assistive-text">Post navigation</h3> <div class="nav-previous"><a href="https://code.flickr.net/tag/api/page/2/" ><span class="meta-nav">←</span> Older posts</a></div> <div class="nav-next"></div> </nav><!-- #nav-above --> <article id="post-3394" class="post-3394 post type-post status-publish format-standard hentry category-api-2 category-open-source tag-api tag-javascript tag-node-js"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2016/04/25/introducing-yakbak-record-and-playback-http-interactions-in-nodejs/" rel="bookmark">Introducing yakbak: Record and playback HTTP interactions in NodeJS</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2016/04/25/introducing-yakbak-record-and-playback-http-interactions-in-nodejs/" title="7:22 pm" rel="bookmark"><time class="entry-date" datetime="2016-04-25T19:22:18-07:00">April 25, 2016</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/jeremyruppel/" title="View all posts by jeremyruppel" rel="author">jeremyruppel</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p><b></b><span style="font-weight:400;">Did you know that the new Front End of <a href="https://www.flickr.com" target="_blank">www.flickr.com</a> is one big Flickr API client? Writing a client for an existing API or service can be a lot of fun, but decoupling and testing that client can be quite tricky. There are many different approaches to taking the backing service out of the equation when it comes to writing tests for client code. Today we’ll discuss the pros and cons of some of these approaches, describe how the Flickr Front End team tests service-dependent libraries, and introduce you to our new NodeJS HTTP playback module: </span><a href="https://github.com/flickr/yakbak"><span style="font-weight:400;">yakbak</span></a><span style="font-weight:400;">!</span></p> <p><b>Scenario: Testing a Flickr API Client</b></p> <p><span style="font-weight:400;">Let’s jump into some code, shall we? Suppose we’re testing a (very, very simple) photo search API client:</span></p> <p><a href="https://gist.github.com/jeremyruppel/fd25c723a5962a49936f174d765aa11a" rel="nofollow">https://gist.github.com/jeremyruppel/fd25c723a5962a49936f174d765aa11a</a></p> <p><span style="font-weight:400;">Currently, this code will make an HTTP request to the Flickr API on every test run. This is less than desirable for several reasons:</span></p> <ul> <li style="font-weight:400;"><a href="https://en.wikipedia.org/wiki/User-generated_content"><i><span style="font-weight:400;">UGC</span></i></a><i><span style="font-weight:400;"> is unpredictable</span></i><span style="font-weight:400;">. In this test, we’re asserting that the response code is an HTTP 200, but obviously our client code needs to provide the response data to be useful. It’s impossible to write a meaningful and predictable test against live content.</span></li> <li style="font-weight:400;"><i><span style="font-weight:400;">Traffic is unpredictable</span></i><span style="font-weight:400;">. This photos search API call usually takes ~150ms for simple queries, but a more complex query or a call during peak traffic may take longer.</span></li> <li style="font-weight:400;"><i><span style="font-weight:400;">Downtime is unpredictable</span></i><span style="font-weight:400;">. Every service has downtime (the term is </span><a href="https://en.wikipedia.org/wiki/High_availability"><span style="font-weight:400;">“four nines,”</span></a><span style="font-weight:400;"> not “one hundred percent” for a reason), and if your service is down, your client tests will fail.</span></li> <li style="font-weight:400;"><i><span style="font-weight:400;">Networks are unpredictable</span></i><span style="font-weight:400;">. Have you ever tried coding on a plane? Enough said.</span></li> </ul> <p><span style="font-weight:400;">We want our test suite to be consistent, predictable, and fast. We’re also only trying to test our client code, not the API. Let’s take a look at some ways to replace the API with a control, allowing us to predictably test the client code.</span></p> <p><b>Approach 1: Stub the HTTP client methods</b></p> <p><span style="font-weight:400;">We’re using </span><a href="https://github.com/visionmedia/superagent"><span style="font-weight:400;">superagent</span></a><span style="font-weight:400;"> as our HTTP client, so we could use a mocking library like </span><a href="http://sinonjs.org/"><span style="font-weight:400;">sinon</span></a><span style="font-weight:400;"> to stub out superagent’s Request methods:</span></p> <p><a href="https://gist.github.com/jeremyruppel/8b837f439663db325aaa2437a2259934" rel="nofollow">https://gist.github.com/jeremyruppel/8b837f439663db325aaa2437a2259934</a></p> <p><span style="font-weight:400;">With these changes, we never actually make an HTTP request to the API during a test run. Now our test is predictable, controlled, and it runs </span><b>crazy fast</b><span style="font-weight:400;">. However, this approach has some major drawbacks:</span></p> <ul> <li style="font-weight:400;"><i><span style="font-weight:400;">Tightly coupled with superagent.</span></i><span style="font-weight:400;"> We’re all up in the client’s implementation details here, so if superagent ever changes their API, we’ll need to correct our tests to match. Likewise, if we ever want to use a different HTTP client, we’ll need to correct our tests as well.</span></li> <li style="font-weight:400;"><i><span style="font-weight:400;">Difficult to specify the full HTTP response</span></i><span style="font-weight:400;">. Here we’re only specifying the </span><span style="font-weight:400;">statusCode</span><span style="font-weight:400;">; what about when we need to specify the body or the headers? Talk about verbose.</span></li> <li style="font-weight:400;"><i><span style="font-weight:400;">Not necessarily accurate</span></i><span style="font-weight:400;">. We’re trusting the test author to provide a fake response that matches what the actual server would send back. What happens if the API changes the response schema? Some unhappy developer will have to manually update the tests to match reality (probably an intern, let’s be honest).</span></li> </ul> <p><span style="font-weight:400;">We’ve at least managed to replace the service with a control in our tests, but we can do (slightly) better.</span></p> <p><b>Approach 2: Mock the NodeJS HTTP module</b></p> <p><span style="font-weight:400;">Every NodeJS HTTP client will eventually delegate to the standard NodeJS http module to perform the network request. This means we can intercept the request at a low level by using a tool like </span><a href="https://www.npmjs.com/package/nock"><span style="font-weight:400;">nock</span></a><span style="font-weight:400;">:</span></p> <p><a href="https://gist.github.com/jeremyruppel/d92a62400f635b42249adc041cdecc96" rel="nofollow">https://gist.github.com/jeremyruppel/d92a62400f635b42249adc041cdecc96</a></p> <p><span style="font-weight:400;">Great! We’re no longer stubbing out superagent and we can still control the HTTP response. This avoids the HTTP client coupling from the previous step, but still has many similar drawbacks:</span></p> <ul> <li style="font-weight:400;"><span style="font-weight:400;">We’re still completely implementation-dependent. If we want to pass a new query string parameter to our service, for example, we’ll also need to add it to the test so that nock will match the request.</span></li> <li style="font-weight:400;"><span style="font-weight:400;">It’s still laborious to specify the response headers, body, etc.</span></li> <li style="font-weight:400;"><span style="font-weight:400;">It’s still difficult to make sure the response body always matches reality.</span></li> </ul> <p><span style="font-weight:400;">At this point, it’s worth noting that none of these bullet points were an issue back when we were actually making the HTTP request. So, let’s do exactly that (once!).</span></p> <p><b>Approach 3: Record and playback the HTTP interaction</b></p> <p><span style="font-weight:400;">The Ruby community created the excellent </span><a href="https://github.com/vcr/vcr"><span style="font-weight:400;">VCR</span></a><span style="font-weight:400;"> gem for recording and replaying HTTP interactions during tests. Recorded HTTP requests exist as “tapes”, which are just files with some sort of format describing the interaction. The basic workflow goes like this:</span></p> <ol> <li style="font-weight:400;"><span style="font-weight:400;">The client makes an actual HTTP request.</span></li> <li style="font-weight:400;"><span style="font-weight:400;">VCR sits in front of the system’s HTTP library and intercepts the request.</span></li> <li style="font-weight:400;"><span style="font-weight:400;">If VCR has a tape matching the request, it simply replays the response to the client.</span></li> <li style="font-weight:400;"><span style="font-weight:400;">Otherwise, VCR lets the HTTP request through to the service, records the interaction to a new tape on disk and plays it back to the client.</span></li> </ol> <p><b>Introducing yakbak</b></p> <p><span style="font-weight:400;">Today we’re open-sourcing </span><a href="https://github.com/flickr/yakbak"><span style="font-weight:400;">yakbak</span></a><span style="font-weight:400;">, our take on recording and playing back HTTP interactions in NodeJS. Here’s what our tests look like with a yakbak proxy:</span></p> <p><a href="https://gist.github.com/jeremyruppel/7050b34342a10d8e3dd8bc2dba0d50c0" rel="nofollow">https://gist.github.com/jeremyruppel/7050b34342a10d8e3dd8bc2dba0d50c0</a></p> <p>Here we’ve created a standard NodeJS http.Server with our proxy middleware. We’ve also configured our client to point to the proxy server instead of the origin service. Look, no implementation details!</p> <p>yakbak tries to do things The Node Way™ wherever possible. For example, each yakbak “tape” is actually its own module that simply exports an http.Server handler, which <span style="font-weight:400;">allows us to do some really cool things. For example, it’s trivial to create a server that always responds a certain way. Since the tape’s hash is based solely on the incoming request, we can easily edit the response however we like. We’re also kicking around a </span><a href="https://github.com/flickr/yakbak/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement"><span style="font-weight:400;">handful of enhancements</span></a><span style="font-weight:400;"> that should make yakbak an even more powerful development tool. </span></p> <p><span style="font-weight:400;">Thanks to yakbak, we’ve been writing fast, consistent, and reliable tests for our HTTP clients and applications. Want to give it a spin? Check it out today: </span><a href="https://github.com/flickr/yakbak"><span style="font-weight:400;">https://github.com/flickr/yakbak</span></a></p> <p><b>P.S. We’re hiring!</b></p> <p><span style="font-weight:400;">Do you love development tooling and helping keep teams on the latest and greatest technology? Or maybe you just want to help build the best home for your photos on the entire internet? </span><a href="https://www.flickr.com/jobs"><span style="font-weight:400;">We’re hiring Front End Ops</span></a><span style="font-weight:400;"> and tons of other great positions. We’d love to hear from you!</span></p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/api-2/" rel="category tag">API</a>, <a href="https://code.flickr.net/category/open-source/" rel="category tag">open source</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/javascript/" rel="tag">javascript</a>, <a href="https://code.flickr.net/tag/node-js/" rel="tag">node.js</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-3394 --> <article id="post-3114" class="post-3114 post type-post status-publish format-standard hentry category-uncategorized tag-api tag-caching tag-data-model tag-eviction tag-model"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2015/09/01/the-data-freshener/" rel="bookmark">The Data Freshener</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2015/09/01/the-data-freshener/" title="4:26 pm" rel="bookmark"><time class="entry-date" datetime="2015-09-01T16:26:49-07:00">September 1, 2015</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/ericsoco/" title="View all posts by ericsoco" rel="author">ericsoco</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <div style="width:340px;"> <a href="https://www.flickr.com/photos/hellomokona/956099245/">https://www.flickr.com/photos/hellomokona/956099245/</a><br /> <em>So fresh</em> </div> <p> </p> <h2>Change</h2> <p>You may have noticed some changes in Flickr a couple months back. Like, half the site changed. 95% even, by some metrics. Some say CHANGE IT BACK! while others welcome change. Whatever your thoughts, the changes are here, and they mean things. For example, they mean new visual design and better usability. They mean a faster site. Unfortunately, up until recently, they also meant more stale data. Yuck.</p> <div style="width:480px;"> <a data-flickr-embed="true" href="https://www.flickr.com/photos/spcbrass/4388396268/" title="Change by spcbrass, on Flickr"><img decoding="async" src="https://farm3.staticflickr.com/2759/4388396268_87224c6556_z.jpg" width="640" height="480" alt="Change"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script><br /> <em>Change</em><br /> </div> <p>Why? What? Well…here’s the deal. We have a new-ish frontend stack we’ve been using for the past couple years now. It’s an <a href="http://isomorphic.net/javascript">isomorphic</a> <a href="https://en.wikipedia.org/wiki/Single-page_application">single-page application</a>, runs on <a href="http://nodejs.org">node.js</a>, and is generally awesome. We call it Reboot.</p> <div style="width:480px;"> <a data-flickr-embed="true" href="https://www.flickr.com/photos/wafer/6897507098/" title="hi there / i am the computer by waferbaby, on Flickr"><img decoding="async" src="https://farm6.staticflickr.com/5466/6897507098_78d4c5a056_z.jpg" width="640" height="425" alt="hi there / i am the computer"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script><br /> <em>Reboot</em><br /> </div> <p>In the World of Reboot, we treat data with kid gloves. We <3 data. We never want to give it up, never want to let it down. Once we pull data from our APIs, we store the fetched data in your browser so that we don’t have to fetch it again the next time it’s needed. This means faster page loads and faster navigation, and less API traffic (and thus a more stable and scalable API). The data cached in your browser exists as long as the current Reboot session — until you refresh or leave Reboot for a non-Rebooted page.</p> <p>However, this also meant that data could become stale. You change the date taken of your photo, someone else adds a comment, you navigate to a page with cached data…and you don’t see the changes. Wat? Yeah. So, this was not a huge problem until we moved lots of pages onto Reboot in the beginning of May. From that point forward, most Flickr user sessions have spent their entirety on Reboot, feeding off the same stale loaves of cached data.</p> <div style="width:480px;"> <a href="https://www.flickr.com/photos/recyclethis/157108084/">https://www.flickr.com/photos/recyclethis/157108084/</a><br /> <em>Staleness</em><br /> </div> <h2>The thinking (design / prototypes)</h2> <p>We considered a number of possibilities for freshening up data during a user session. A brief history of the strategies we sampled, and their results:</p> <h3>1. Refresh on update</h3> <div style="width:480px;"> <a data-flickr-embed="true" href="https://www.flickr.com/photos/the_family_farm/2571858493/" title="Ice Tea by MzScarlett / A.K.A. Michelle, on Flickr"><img loading="lazy" decoding="async" src="https://farm4.staticflickr.com/3075/2571858493_34a1f75ac3_z.jpg" width="640" height="480" alt="Ice Tea"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script><br /> </div> <p>The first stab focused on updating data locally after it was changed by the user. Most of our simpler use cases already updated as expected, but some trickier cases with indirect relationships did not. For example, changing the date taken of a photo updated the data model for the photo, but deleting a photo did not necessarily ensure the photo was removed from all the cached albums, groups, and galleries to which it belonged. (Note that the photo was removed correctly from the backend, just not from the cached representation of those entities on the client.)</p> <p>Cleaning up these relationships using change events between models helped, but didn’t solve all our problems. When someone outside of the local session (read: another user) changed data, it would not reflect in the current session. The only way to catch changes from outside the current session was to be more aggressive about evicting models.</p> <h3>2. Nuclear option</h3> <div style="width:480px;"> <a href="https://www.flickr.com/photos/sdasmarchives/8091816484/">https://www.flickr.com/photos/sdasmarchives/8091816484/</a><br /> </div> <p>The pendulum swung all the way in the other direction — instead of surgical removal of data models we knew to be out-of-date, what would happen if we removed all cached data on every navigation? This prototype was quick to build, and incredibly destructive. By doing this, all our cached data always remained as fresh as could be, but we essentially reverted to Web 1.0 — with the exception of the Reboot framework, everything was reloaded on every page.</p> <p>Not surprisingly, this blew up API traffic (locally only! did not unleash that disaster at scale), and inflated page load times like a Jeff Koons sculpture. It did give us some baseline timing metrics we could point to as worst-case scenarios, however. The next step was to swing the pendulum back toward the middle — to a carefully-knitted solution that would preserve fast page loads and navigation, while ensuring the freshest data we could serve up.</p> <h3>3. Refetch on navigate</h3> <div style="width:340px;"> <a data-flickr-embed="true" href="https://www.flickr.com/photos/daveemerson/4296539211/" title="fetched by Dave's Domain, on Flickr"><img loading="lazy" decoding="async" src="https://farm5.staticflickr.com/4029/4296539211_c054e6b762_z.jpg" width="498" height="640" alt="fetched"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script><br /> </div> <p>At this point, our challenge was to find a solution that would keep navigation fast, API traffic slim, and pick up all changes to session data, whether local or remote. We ended up with a solution we call “refetching”: evicting and requesting new data models as the model is needed by the application. But when?<br /> We could refetch periodically or on a user action; we determined that the best time to trigger a refetch was on navigation — when the user navigates, cached models become eligible for refetching. Specifically, when the user navigates between sections of the site, refetching is triggered. This proved to be the happiest medium between speed and freshness.</p> <p>A high-level outline of how the refetching strategy works:</p> <ul> <li>The user loads a page; data are requested from the API, and models are cached. As new models are created, they’re marked as being fresh.</li> <li>The user navigates to another site section (e.g. Photostream → Search); all freshness marks are removed from all models. They’re now all eligible for refetching.</li> <li>As Reboot builds the new page, it requests data models from the cache. Since they no longer have their seal of freshness, they are refetched, and marked as fresh once retrieved and cached.</li> </ul> <p>One important note — refetching is not triggered on browser back/forward navigation. Users expect near-immediate navigation, thanks to browser caching, when navigating to already-viewed content. Therefore, we refetch only when the user clicks a link to navigate to a new site section.</p> <h3>4. Miscellany</h3> <p>There were a couple other options we considered and rejected from the start, but they’re worth mentioning here.</p> <p>One was a <a href="https://en.wikipedia.org/wiki/Time_to_live">TTL (time-to-live) algorithm</a>, commonly used in caching applications. TTL algorithms expire data and evict from the cache a certain amount of time after they’re written or last updated. The arbitrary nature of TTL would mean that users would sometimes have fresh data and sometimes stale; it would be fresh more often than without any solution, but freshness would vary arbitrarily and would not result in much of an improvement on user experience.</p> <p>The other was to write an algorithm that tracks the amount of time since a data model was last accessed, and refetch when it grows too old. While this sounded interesting at first, it has the same flaw as a standard TTL algorithm — freshness becomes arbitrary. It’s also more complex to implement, and might end up not being worth the complexity.</p> <h2>The doing (implementation)</h2> <p>So that was it! Refetch on navigate, all done. Right?….of course not. With the general strategy in place, the devil started sneaking around in all the details. Some of the highlights:</p> <h3>Exemptions</h3> <p>It proved to be not the best idea to evict on all navigation. For example, in Reboot we often preload photo metadata models on pages with lists of photos, in order to make navigation into the photo page snappy. The refetch setup therefore has an exemption config that allows us to easily retain models when navigating into, away from, or between specific site sections.</p> <h3>Child models</h3> <p>We often have parent-child associations between data models. For example, the data model for a photo has a reference to a data model for the author of the photo. When the photo model is refetched, the person model must be refetched as well. This means the function doing the eviction and refetching has to recurse through all child models.</p> <h3>Collections</h3> <p>An issue similar to child models above, but more complex, is the case of a model containing a list of other models. For example, the data model for a person’s photostream contains a list of photo models.</p> <p>What made this particularly tricky is pagination and filtering — say you load the first 2 pages of your photostream, set your view filter to private, jump to page 5, switch the view to “Date taken”, and navigate away and back to your photostream…imagine the mess of different models with partially-loaded collections. Evicting one parent model, and its children, might evict photo models from the collection within another, without properly refetching. The solution here actually lay in the controller responsible for fetching pages: if a requested page of models is not already completely in-cache, a refetch will always happen to ensure we have all the data, in its freshest state.</p> <h3>Refetch only once per page view</h3> <p>Critical to the refetch-on-navigation strategy is to refetch only once per navigation. This was not too difficult, but essential to get right. We accomplish this by adding a flag when a model is initially fetched and upserted into the cache. When navigating to a new, non-exempt site section, all those flags are cleared, and any model requested by the new page will be refetched. When refetched, the model is again upserted into the cache and marked as fresh, until the next navigation.</p> <h2>But did it fresh?</h2> <div style="width:480px;"> <a data-flickr-embed="true" href="https://www.flickr.com/photos/libellule24/4438085129/" title="Go on without me by Senorita Lena, on Flickr"><img loading="lazy" decoding="async" src="https://farm5.staticflickr.com/4019/4438085129_58cbbeb2f9_z.jpg" width="640" height="427" alt="Go on without me"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script><br /> </div> <p>With the thinking and the doing out of the way, it was time to push all this to production. Because these changes are essentially pulling the rug out from underneath the data layer on every navigation, we had to tread very carefully in order to prevent any negative impact to the end user experience.</p> <p>We did very thorough manual and automated testing across all of Reboot. We left the feature turned on for staff users for a while, to be able to respond to any bug reports. Finally, the time came to test on Real People. There were three things we needed to keep an eye on: errors (of course), impact on page navigation timing, and API traffic. Since refetching implies more requests for data, we needed to be sure that we were keeping the user experience smooth and fast, and also that we weren’t blowing up our data centers.</p> <div style="width:480px;"> <br /> <a href="https://www.flickr.com/photos/karolfranks/6296290871/">https://www.flickr.com/photos/karolfranks/6296290871/</a><br /> <em>All in</em><br /> </div> <p>In order to get a good read on these things, though, we had to go all in. Letting in just a small percentage of users would not give reliable numbers for timing or traffic impacts, due to the noise inherent in relatively small sample sizes. So, we did something unusual: we turned on refetching for all users for a short period of time. We flipped on refetching and kept an eagle eye on our stats for 2 hours, then reverted; then, we took a careful look at the aggregated data to see how the experiment went.</p> <p>Surprisingly, the impact on both timing and traffic was relatively low. After some thought, we decided this is most likely because the changes disproportionately impact people on long sessions, say a Flickr tab open for hours or days. Most people don’t hang around that long; they come, they go. Also, the photo page represents north of 90% of our page views, and is exempt from refetching (see Exemptions above).</p> <p>So where did we end up? A negligible bump in navigation timing and API traffic, and fresher data for all. Perhaps an anticlimactic resolution, but the story we’ve heard today outlines a serious consideration for anyone building an application with a data caching layer: keep in mind from the beginning how you plan to deal with stale data, but in a way that keeps all the other benefits of a single-page application.</p> <div style="width:480px;"> <a data-flickr-embed="true" href="https://www.flickr.com/photos/pinguino/6800265589/" title="#CCC is a breadcat by pinguino, on Flickr"><img loading="lazy" decoding="async" src="https://farm8.staticflickr.com/7151/6800265589_3204a4723b_z.jpg" width="640" height="427" alt="#CCC is a breadcat"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script><br /> <em>Busting through staleness. Yep.</em> </div> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/caching/" rel="tag">caching</a>, <a href="https://code.flickr.net/tag/data-model/" rel="tag">data model</a>, <a href="https://code.flickr.net/tag/eviction/" rel="tag">eviction</a>, <a href="https://code.flickr.net/tag/model/" rel="tag">model</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-3114 --> <article id="post-1925" class="post-1925 post type-post status-publish format-standard hentry category-uncategorized tag-api tag-groups"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2012/05/24/group-apis/" rel="bookmark">Group APIs</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2012/05/24/group-apis/" title="6:05 pm" rel="bookmark"><time class="entry-date" datetime="2012-05-24T18:05:59-07:00">May 24, 2012</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/jfanaian/" title="View all posts by jfanaian" rel="author">jfanaian</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p>With over 1.5 million <a href="http://www.flickr.com/groups">groups</a>, it’s no doubt that they are an important part of Flickr. Today, we’re releasing a few new ways to interact with groups using our API.</p> <h3>Group Membership</h3> <p class="undersized-image-container"> <a href="http://www.flickr.com/photos/fofurasfelinas/369354361/" title="Cat meeting... by fofurasfelinas, on Flickr"><img loading="lazy" decoding="async" src="http://farm1.staticflickr.com/155/369354361_62d1976e82.jpg" width="500" height="333" alt="Cat meeting..."></a> </p> <p>We are adding two new methods to manage group membership through the API.</p> <p><a href="http://www.flickr.com/services/api/flickr.groups.join.html">flickr.groups.join</a> to join a group. Before calling this method, check if the group has rules using <a href="http://www.flickr.com/services/api/flickr.groups.getInfo.html">flickr.groups.getInfo</a>. The user needs to agree to the rules before being able to join the group. Pass the accept_rules argument if the user accepted the rules.</p> <p><a href="http://www.flickr.com/services/api/flickr.groups.leave.html">flickr.groups.leave</a> to leave a group. The user’s photos can also be deleted when leaving the group by passing the delete_photos argument.</p> <h3>Group Discussions</h3> <p class="undersized-image-container"> <a href="http://www.flickr.com/photos/ohh_rissa/3472376226/" title="shut UP WALTON by larissa_allen, on Flickr"><img loading="lazy" decoding="async" src="http://farm4.staticflickr.com/3558/3472376226_6d62aaa1e4.jpg" width="500" height="333" alt="shut UP WALTON"></a> </p> <p>We are also opening up group discussions in the API. You can now fetch a list of discussion topics for a group using <a href="http://www.flickr.com/services/api/flickr.groups.discuss.topics.getList.html">flickr.groups.discuss.topics.getList</a>, with sticky topics first, then regular topics sorted from newest to oldest.</p> <pre class="brush: xml; gutter: false; title: ; notranslate" title=""> &lt;rsp stat=&quot;ok&quot;&gt; &lt;topics group_id=&quot;46744914@N00&quot; iconserver=&quot;1&quot; iconfarm=&quot;1&quot; name=&quot;Tell a story in 5 frames (Visual story telling)&quot; members=&quot;12428&quot; privacy=&quot;3&quot; lang=&quot;en-us&quot; ispoolmoderated=&quot;1&quot; total=&quot;4621&quot; page=&quot;1&quot; per_page=&quot;2&quot; pages=&quot;2310&quot;&gt; &lt;topic id=&quot;72157625038324579&quot; subject=&quot;A long time ago in a galaxy far, far away...&quot; author=&quot;53930889@N04&quot; authorname=&quot;Smallportfolio_jm08&quot; role=&quot;member&quot; iconserver=&quot;5169&quot; iconfarm=&quot;6&quot; count_replies=&quot;8&quot; can_edit=&quot;0&quot; can_delete=&quot;0&quot; can_reply=&quot;0&quot; is_sticky=&quot;0&quot; is_locked=&quot;&quot; datecreate=&quot;1287070965&quot; datelastpost=&quot;1336905518&quot;&gt; &lt;message&gt; ... &lt;/message&gt; &lt;/topic&gt; &lt;/topics&gt; &lt;/rsp&gt; </pre> <p><a href="http://www.flickr.com/services/api/flickr.groups.discuss.topics.add.html">flickr.groups.discuss.topics.add</a> to post a new topic to a group, passing a subject and the message content.</p> <p>Additionally, you can fetch a list of replies for a topic using <a href="http://www.flickr.com/services/api/flickr.groups.discuss.replies.getList.html">flickr.groups.discuss.replies.getList</a>, which includes the information for the topic along with all the replies, sorted from oldest to newest.</p> <pre class="brush: xml; gutter: false; title: ; notranslate" title=""> &lt;rsp stat=&quot;ok&quot;&gt; &lt;replies&gt; &lt;topic topic_id=&quot;72157625038324579&quot; subject=&quot;A long time ago in a galaxy far, far away...&quot; group_id=&quot;46744914@N00&quot; iconserver=&quot;1&quot; iconfarm=&quot;1&quot; name=&quot;Tell a story in 5 frames (Visual story telling)&quot; author=&quot;53930889@N04&quot; authorname=&quot;Smallportfolio_jm08&quot; role=&quot;member&quot; author_iconserver=&quot;5169&quot; author_iconfarm=&quot;6&quot; can_edit=&quot;0&quot; can_delete=&quot;0&quot; can_reply=&quot;0&quot; is_sticky=&quot;0&quot; is_locked=&quot;&quot; datecreate=&quot;1287070965&quot; datelastpost=&quot;1336905518&quot; total=&quot;8&quot; page=&quot;1&quot; per_page=&quot;3&quot; pages=&quot;2&quot;&gt; &lt;message&gt; ... &lt;/message&gt; &lt;/topic&gt; &lt;reply id=&quot;72157625163054214&quot; author=&quot;41380738@N05&quot; authorname=&quot;BlueRidgeKitties&quot; role=&quot;member&quot; iconserver=&quot;2459&quot; iconfarm=&quot;3&quot; can_edit=&quot;0&quot; can_delete=&quot;0&quot; datecreate=&quot;1287071539&quot; lastedit=&quot;0&quot;&gt; &lt;message&gt; ... &lt;/message&gt; &lt;/reply&gt; &lt;/replies&gt; &lt;/rsp&gt; </pre> <p><a href="http://www.flickr.com/services/api/flickr.groups.discuss.replies.add.html">flickr.groups.discuss.replies.add</a> to post a reply to a topic, passing the message content.</p> <p><a href="http://www.flickr.com/services/api/flickr.groups.discuss.replies.edit.html">flickr.groups.discuss.replies.edit</a> to edit a reply, passing the updated message.</p> <p><a href="http://www.flickr.com/services/api/flickr.groups.discuss.replies.delete.html">flickr.groups.discuss.replies.delete</a> to delete a reply.</p> <p>You can only edit and delete replies when authorized as the owner of the reply. For now, it is not possible to edit or delete a topic through the API.</p> <p>If you have any questions, comments, concerns, or just want to chat about these methods or anything else related to the API, please join the <a href="http://tech.groups.yahoo.com/group/yws-flickr">Flickr Developer mailing list</a>.</p> <p>Photos from <a href="http://www.flickr.com/photos/fofurasfelinas/">fofurasfelinas</a> and <a href="http://www.flickr.com/photos/ohh_rissa/3472376226/">larissa_allen</a>.</p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/groups/" rel="tag">groups</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-1925 --> <article id="post-1532" class="post-1532 post type-post status-publish format-standard hentry category-uncategorized tag-api tag-feed tag-push"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2011/06/30/dont-be-so-pushy/" rel="bookmark">Don’t be so PuSHy</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2011/06/30/dont-be-so-pushy/" title="4:00 pm" rel="bookmark"><time class="entry-date" datetime="2011-06-30T16:00:49-07:00">June 30, 2011</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/flickrphotography/" title="View all posts by Kay Kremerskothen" rel="author">Kay Kremerskothen</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p class="p1">You know three things that would be cool?</p> <ul class="ul1"> <li class="li2">the ability to subscribe to the output of a Flickr API call in a feed aggregator</li> <li class="li2">the ability to get the results of Flickr API calls as…</li> </ul> <p class="p2"> <p class="p2">Oh wait. <a href="http://code.flickr.com/blog/2008/08/25/api-responses-as-feeds/">That</a> was a while ago. Wouldn’t it be great if you didn’t have to poll our API over and over just to see if photos were there, only to find out you waited too long since last time and now because of the results size limits you can’t get them all and you have to figure out how many you missed and then you have to make another call with the right offset to get those results but of course in between then and now the result set changed a bit so you aren’t sure if you really got them all and…</p> <p class="p2">Wouldn’t it be great if Flickr had something that could just PuSH photos to you as they appeared, kind of like <a href="http://www.aaronland.info/weblog/2011/05/07/fancy/#likeadog">this</a>?</p> <p class="p2"><span class="s1">Introducing the new (and experimental) flickr.push API methods. These allow you to subscribe to new uploads and updates from your contacts and favorites from your contacts. Let’s dive right in and see exactly how it all works:</span></p> <p><a title=""sky captain" by straup, on Flickr" href="http://www.flickr.com/photos/straup/5883650870/"><img loading="lazy" decoding="async" src="http://farm6.static.flickr.com/5276/5883650870_4003695c96.jpg" border="0" alt=""there is a virtuous circle in this ecosystem"" width="500" height="375" /></a></p> <p>The 20,000 ft overview is basically this:</p> <ol> <li>You make an API call to Flickr asking to subscribe to one of several different photo feeds, providing a callback URL in the arguments.</li> <li>A little verification dance ensues during which we make a request to your callback URL. If you respond appropriately we’re all good and from then on…</li> <li>Live(-ish) updates are POSTed from Flickr to your callback URL in Atom 1.0 format.</li> </ol> <p></p> <h2>Subscribing</h2> <p>The subscription system is based as closely as possible on Google’s <a href="http://code.google.com/p/pubsubhubbub/" target="_blank">Pubsubhubbub protocol</a>, with a few wrinkles. One is that Flickr acts as the hub and the publisher all rolled in to one. We’re obviously not really “publishing” separate feeds of every single user’s contacts’ photos and faves to a central hub somewhere, we only create the feeds on demand when someone subscribes to them. So we couldn’t, for example, publish them all to a 3rd-party hub like <a href="http://superfeedr.com/" target="_blank">Superfeedr</a>. But the whole pubsubhubbub metaphor still works pretty well.</p> <p class="p2">Another difference is that the subscription happens via an authenticated API call and not an <code>HTTP POST</code>; hopefully the reasons for this are obvious. We’ll get into them in detail a little bit later. But even though the mechanism for the subscription request is different we’ve tried to follow the protocol as closely as possible and keep the parameters the same. The Google PubSubHubbub Core 0.3 section on <a href="http://pubsubhubbub.googlecode.com/svn/trunk/pubsubhubbub-core-0.3.html#subscribing" target="_blank">how the subscription flow works</a> is a good place to start, and the rest of this post assumes you’ve read that and more or less understand what the interactions should be between hub and subscriber. Done? OK, here are the methods:</p> <p><code>flickr.push.getTopics</code></p> <p class="p2">This method just tells you what you can subscribe to. It returns something like this:</p> <pre><<span class="s1">rsp</span> stat="<span class="s2">ok</span>"> <span class="s3"> <</span>topics<span class="s3">> </span> <<span class="s1">topic</span> name="<span class="s2">contacts_photos</span>" /> <<span class="s1">topic</span> name="<span class="s2">contacts_faves</span>" /> <span class="s3"> <</span>/topics<span class="s3">> </span><span class="s3"><</span>/rsp<span class="s3">></span></pre> <p class="p2"> <p class="p2">yeah, yeah, you already get that part. You can currently subscribe to contacts’ photos (new uploads and updates) or contacts’ faves. So subscribe already!</p> <p><code>flickr.push.subscribe</code></p> <p class="p2">This method (which requires an authentication token with read permissions) takes almost exactly the same arguments as a “proper” PubSubHubbub subscribe HTTP request would. Wee differences:</p> <p style="padding-left: 30px;"><code>topic</code> – unlike the topic argument in the HTTP version (which is a URL), this is just one of the topic types returned by <code>flickr.push.getTopics</code>.</p> <p style="padding-left: 30px;"><code>secret</code> – currently not supported, so this parameter is omitted.</p> <p style="padding-left: 30px;"><code>callback</code> – this must be unique, i.e. you can’t use the same URL for more than one subscription.</p> <p class="p2">Everything else works as you would expect – verification (either synchronous or asynchronous), the hub challenge string, subscription expiration/refreshing, unsubscribing (with <code>flickr.push.unsubscribe</code>) etc. Which brings us to:</p> <p><code>flickr.push.getSubscriptions</code></p> <p class="p2">This method also requires an authentication token with read permissions, and returns a list of subscriptions for the authenticated user, like so:</p> <pre><<span class="s1">rsp</span> stat="<span class="s2">ok</span>"> <span class="s3"> <</span>subscriptions<span class="s3">> </span> <<span class="s1">subscription</span> topic="<span class="s2">contacts_photos</span>" callback="<span class="s2">http://example.com/contacts_photos_endpoint?user=12345</span>" pending="<span class="s2">0</span>" date_create="<span class="s2">1309293755</span>" lease_seconds="<span class="s2">0</span>" expiry="<span class="s2">1309380155</span>" verify_attempts="<span class="s2">0</span>" /> <<span class="s1">subscription</span> topic="<span class="s2">contacts_faves</span>" callback="<span class="s2">http://example.com/contacts_faves_endpoint?user=12345</span>" pending="<span class="s2">0</span>" date_create="<span class="s2">1309293785</span>" lease_seconds="<span class="s2">0</span>" expiry="<span class="s2">1309380185</span>" verify_attempts="<span class="s2">0</span>" /> <span class="s3"> <</span>/subscriptions<span class="s3">> </span><span class="s3"><</span>/rsp<span class="s3">></span></pre> <p class="p2"> <p class="p2">Oh yeah, the docs:</p> <p><a href="http://www.flickr.com/services/api/flickr.push.subscribe.html">flickr.push.subscribe</a><br /> <a href="http://www.flickr.com/services/api/flickr.push.unsubscribe.html">flickr.push.unsubscribe</a><br /> <a href="http://www.flickr.com/services/api/flickr.push.getTopics.html">flickr.push.getTopics</a><br /> <a href="http://www.flickr.com/services/api/flickr.push.getSubscriptions.html">flickr.push.getSubscriptions</a></p> <h2>Feeds</h2> <p class="p2">The format of the feed that gets posted to your endpoint is currently limited to Atom 1.0, i.e. exactly what you’d get from something like</p> <p style="padding-left: 30px;"><code><a href="http://api.flickr.com/services/feeds/photos_public.gne?id=_YOUR_NSID_HERE_&format=atom" rel="nofollow">http://api.flickr.com/services/feeds/photos_public.gne?id=_YOUR_NSID_HERE_&format=atom</a></code></p> <p>For the contacts_faves topic type it’s the same thing but with the addition of the <a href="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.contributor">atom:contributor</a> element to indicate the user who faved the photo.</p> <p class="p2"><span class="s3">And that’s about it. Questions?</span></p> <h2><span class="s3">Privacy and Restrictions</span></h2> <p class="p2"><span class="s3">The astute observer may notice that not all photos are being sent in the PuSH feeds. Since this is a new (and experimental) feature for Flickr, we’ve basically turned all of the privacy/safety restrictions on it up to 11, at least to start with. PuSH feeds currently only contain images that have public visibility and safe adultness level. In addition, users with their “Who can access your original image files” option set to anything other than “anyone” and users who are opted out of the API will not have their photos included in PuSH feeds.</span></p> <p class="p2"><span class="s3">While this may be a bit restrictive (for example since the API call is authenticated and the photos are coming from your contacts technically you should be allowed to see contacts only or friends/family photos for contacts that allow it), we feel that since this is a new thing it’s better to start conservative and see how the feature is being used. It’s possible that we may relax some of these restrictions in the future, but for now a PuSH feed is essentially what a signed-out user could get just by grabbing the RSS feeds from various people’s photostreams.</span></p> <p class="p2"><span class="s3">You will also notice that for now we’ve limited the feature to pro account holders only.</span></p> <h2>So… What?</h2> <p><a title="screens beget BACON!!! by straup, on Flickr" href="http://www.flickr.com/photos/straup/5843200417/"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3045/5843200417_ef63ec2347.jpg" alt="screens beget BACON!!!" width="500" height="374" /></a></p> <p>So what can you do with it? There’s the obvious: any web application which currently does some kind of polling of the Flickr API to get photos for its users can potentially be altered to receive the push feeds instead. More timely updates, cheaper/simpler for the application and as it turns out cheaper for Flickr, too – it’s often easier on our servers to push out events shortly after they happen and we’ve got them (often fresh in our cache) than it is to go and dig them up when they’re asked for some time later.</p> <p><a href="http://www.flickr.com/photos/straup/5891080433/" title="Surprise! by straup, on Flickr"><img loading="lazy" decoding="async" src="http://farm7.static.flickr.com/6024/5891080433_c2628ce5af.jpg" width="500" height="374" alt="Surprise!"></a></p> <p>Some of the more interesting things that we hope these API methods will enable revolve around the more real-time nature of the events they expose. As an example of what’s possible in this space, Aaron Cope has created a little application he calls “Pua”. It’s a wonderfully simple way to surf Flickr without having to do much of anything; Pua takes you on a ride through your contacts’ photos and favorites, as they happen. <a href="http://pua.spum.org/about">Have a read</a> about exactly what it is, why it’s called Pua and why he made it. If you ask nicely maybe Pua will give you an invite code.</p> <h2><span class="s3">Later</span></h2> <p class="p2"><span class="s3">Hopefully there will be much more to come. Finer-grained controls on the subscriptions (safety levels, visibility levels, restricting to just new uploads or only certain types of updates, lightweight JSON feeds, etc.), new types of subscriptions (photos of your friends/family, photos from a particular location, photos having a particular tag, something to do with galleries…), and maybe some other stuff we haven’t thought of yet. Hey, </span>wouldn’t it be cool if you didn’t need to run a web server on the other end to be the endpoint of the feeds?</p> <p class="p2"><span class="s3">Let us know what you’d like to see! What works, what doesn’t, what we got wrong and how to make it more useful to the people who want to Build Stuff (that’s you).</span></p> <h2><span class="s3">Fine Print</span></h2> <p><a title=""there is a virtuous circle in this ecosystem" by straup, on Flickr" href="http://www.flickr.com/photos/straup/2228947655/"><img loading="lazy" decoding="async" src="http://farm3.static.flickr.com/2151/2228947655_f954471d29.jpg" border="0" alt=""there is a virtuous circle in this ecosystem"" width="500" height="375" /></a></p> <p class="p2"><span class="s3">The Flickr PuSH feeds are part of the Flickr API, and thus fall under the <a href="http://www.flickr.com/services/api/tos/" target="_blank">API Terms of Service Agreement</a>. This means all the usual things about respecting photo owners’ copyrights and all also the other good bits about API abuse. In other words, don’t try to subscribe to all of Flickr. Trust me, we’ll notice.</span></p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/feed/" rel="tag">feed</a>, <a href="https://code.flickr.net/tag/push/" rel="tag">push</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-1532 --> <article id="post-1519" class="post-1519 post type-post status-publish format-standard hentry category-uncategorized tag-api tag-auth tag-oauth"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2011/06/21/flickr-now-supports-oauth-1-0a/" rel="bookmark">Flickr now Supports OAuth 1.0a</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2011/06/21/flickr-now-supports-oauth-1-0a/" title="12:00 am" rel="bookmark"><time class="entry-date" datetime="2011-06-21T00:00:56-07:00">June 21, 2011</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/jfanaian/" title="View all posts by jfanaian" rel="author">jfanaian</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p>We’re happy to announce that Flickr now supports OAuth! This is an open standard for authentication, which is now fully supported by the Flickr API. You can get started by going to our <a href="http://www.flickr.com/services/api/auth.oauth.html">OAuth documentation</a>. As part of this announcement, we would also like to note that the old Flickr authentication is now deprecated, and is expected to be disabled early 2012.</p> <div style="margin-top: 30px;margin-bottom: 30px"> <p><a title="I'm Guarding the Door por Frenck's Photography, en Flickr" href="http://www.flickr.com/photos/frenck/2923374863/"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3051/2923374863_f5f11d94f0.jpg" alt="I'm Guarding the Door" width="333" height="500" /></a></p> <div style="font-size: small">I’m Guarding the Door by <a href="http://www.flickr.com/photos/frenck/">Frenck’s Photography</a></div> </div> <p>OAuth is very similar to the old Flickr auth in a lot of ways. You start by getting a request token (frob in the old flow), redirecting the user to the authentication page, and then getting a token which can be used to make authenticated requests. With proper OAuth support, though, you will be able to use one of the many libraries available in a variety of languages to get started.</p> <p>In addition to this, we have streamlined the authentication process across desktop, mobile and web, and have simplified the user experience by removing the anti-phishing step for the Desktop flow, which is no longer necessary.</p> <p>Currently, we only support OAuth 1.0a, but we have plans to eventually support OAuth 2.0. The decision was based on the fact that OAuth 2.0 is still an evolving definition that is rapidly changing.</p> <p>We wanted to make the transition to OAuth seamless to the user, so we created a method to exchange an old token, with an OAuth token. The application has to simply make an authenticated request to <a href="http://www.flickr.com/services/api/flickr.auth.oauth.getAccessToken.html">flickr.auth.oauth.getAccessToken</a>, which returns an OAuth auth token and signature for that user which are tied to your application. The exchange is meant to be final, so the old authentication token is scheduled to expire 24 hours after this API method is called.</p> <p>Now, it’s your turn! Go read our <a href="http://www.flickr.com/services/api/auth.oauth.html">OAuth documentation</a> if you already have an application, or visit our <a href="http://www.flickr.com/services/developer">developer guide</a> for more information on how to get started. If you experience any problems, or have any questions or suggestions regarding our OAuth implementation, please post to our <a href="http://tech.groups.yahoo.com/group/yws-flickr/">developer mailing list</a>.</p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/auth/" rel="tag">auth</a>, <a href="https://code.flickr.net/tag/oauth/" rel="tag">oauth</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-1519 --> <article id="post-1518" class="post-1518 post type-post status-publish format-standard hentry category-uncategorized tag-api tag-api-explorer tag-kittens"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2011/06/01/refreshing-the-api-explorer/" rel="bookmark">Refreshing The API Explorer</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2011/06/01/refreshing-the-api-explorer/" title="9:42 pm" rel="bookmark"><time class="entry-date" datetime="2011-06-01T21:42:03-07:00">June 1, 2011</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/flickrphotography/" title="View all posts by Kay Kremerskothen" rel="author">Kay Kremerskothen</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p>Most people know that Flickr has an API. As it wouldn’t be much use without <a href="http://www.flickr.com/services/api/">documentation</a>, we have that, too. (There’s even a <a href="http://www.flickr.com/services/api/flickr.reflection.getMethods.html">list of methods</a> and <a href="http://www.flickr.com/services/api/flickr.reflection.getMethodInfo.html">information about each</a> available via the API itself.) What if I told you there was <em>also</em> a way to experiment with it from the comfort of your browser, no coding required?</p> <div style="margin-top:30px;margin-bottom:30px"> <a href="http://www.flickr.com/photos/zabowski/2911616552/" title="Sink Explorer by Zabowski, on Flickr"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3191/2911616552_002c0d337c.jpg" width="500" height="333" alt="Sink Explorer" style="border:1px dotted #ccc;padding:10px"></a></p> <div style="font-size:small;text-align:right">Sink Explorer by <a href="http://www.flickr.com/photos/zabowski/">Zabowski</a></div> </div> <p>Well, that’s what Flickr’s API Explorer offers. It’s an easy way to customise requests by filling in simple form fields, whether the method requires authentication or not, and to see the responses that are returned. It’s great for one-off prototype scripts where you quickly want to find some data, for seeing whether a method does what you think it does, or to sanity-check some code that’s not doing the right thing.</p> <p>It’s been around for years, but nobody ever seems to have made much of a fuss about it. (The only mention I can find on this blog is an interview with a certain <a href="http://code.flickr.com/blog/2008/11/06/5-questions-for-paul-mison/">API developer</a> singing its praises.) However, it’s needed a little attention to bring it up to date, and so I made some time to teach it a few new tricks.</p> <p>Firstly, it now offers a choice of output response. While it doesn’t offer every format that the API does, the three (and a bit) available – the default <a href="http://www.flickr.com/services/api/response.rest.html">XML</a>, <a href="http://www.flickr.com/services/api/response.json.html">JSONP</a> (or raw JSON), and <a href="http://www.flickr.com/services/api/response.php.html">PHP</a> serialized data – should cover a lot of ground. Secondly, the Explorer pages now have proper URLs, so it’s possible to link to the API method for fetching the <a href="http://www.flickr.com/services/api/explore/flickr.panda.getList">list of pandas</a>, for example. Finally, for the most popular of those response types – XML and JSON(P) – responses are now pretty-printed and syntax highlighted, as are the examples in the API documentation pages for each method. That is to say, the returned values are indented and have line breaks, while the name, attributes and quoted values of the elements are coloured appropriately.</p> <p>Now that you know that it exists, and that it’s all freshened up with spiffy features, why not go and <a href="http://www.flickr.com/services/api/explore/flickr.photos.search">play around</a>? Have fun!</p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/api-explorer/" rel="tag">api explorer</a>, <a href="https://code.flickr.net/tag/kittens/" rel="tag">kittens</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-1518 --> <article id="post-1324" class="post-1324 post type-post status-publish format-standard hentry category-uncategorized tag-api tag-galleries"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2010/04/08/galleries-apis/" rel="bookmark">Galleries APIs</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2010/04/08/galleries-apis/" title="4:49 pm" rel="bookmark"><time class="entry-date" datetime="2010-04-08T16:49:31-07:00">April 8, 2010</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/flickrphotography/" title="View all posts by Kay Kremerskothen" rel="author">Kay Kremerskothen</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p><a href="http://www.flickr.com/photos/effigie/3174724777/" title="Giant Isopod by Effigie, on Flickr"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3134/3174724777_be36f78ab0.jpg" width="500" height="333" alt="Giant Isopod" /></a></p> <p><a href="http://www.flickr.com/galleries">We love galleries</a>. After all, without galleries how would you find your <a href="http://www.flickr.com/photos/yahooeditorspicks/galleries/72157623618461809/">giant sea bugs</a>? </p> <p>This post is to quickly announce we’ve added galleries to <a href="http://flickr.com/services/api/">the API.</a></p> <h3 id="a_roseguid_by_any_other_name_8230">A Rose+GUID by Any Other Name ….</h3> <p>Galleries in the API use “compound-ids”. Like tags. An example gallery compound id might look like <a href="http://www.flickr.com/photos/revdancatt/galleries/72157621980433950"><code>9634-72157621980433950</code></a>. Unlike photos you can’t simply grab the last number off a gallery url and stick it into the API. Yeah, I’m not thrilled about it either, but there are good (read boring) reasons why it works that way. </p> <p>So when an API method says it takes a <code>gallery_id</code>, we’re talking about the compound-id.</p> <p>You can however use the <a href="http://www.flickr.com/services/api/flickr.urls.lookupGallery.html">flickr.urls.lookupGallery</a> method to go from gallery url to <code>gallery_id</code>. Pass the method the URL for the gallery, and we’ll give you back the gallery info blob.</p> <p>You can also get gallery IDs from <a href="http://www.flickr.com/services/api/flickr.galleries.getList.html">flickr.galleries.getList</a> and <a href="http://www.flickr.com/services/api/flickr.galleries.getInfo.html">flickr.galleries.getInfo</a>.</p> <h3 id="anatomy_of_a_gallery">Anatomy of a Gallery</h3> <p>Behold, a gallery info blob:</p> <pre><code><gallery id="6065-72157617483228192" url="http://www.flickr.com/photos/straup/galleries/72157617483228192" owner="35034348999@N01" primary_photo_id="292882708" primary_photo_server="112" primary_photo_farm="1" primary_photo_secret="7f29861bc4" date_create="1241028772" date_update="1270111667" count_photos="17" count_videos="0" > <title>Cat Pictures I've Sent To Kevin Collins</title> <description>dive dive dive</description> </gallery> </code></pre> <p>The <code>primary_photo_*</code> attributes refer to the “cover photo” for the gallery. The owner is the Flickr <code>user_id</code> (aka <code>NSID</code>) of the member who created the gallery. The id is that compound-id we talked about.</p> <h3 id="lists_of_galleries">Lists of Galleries</h3> <p>You can fetch all of a member’s galleries using <a href="http://www.flickr.com/services/api/flickr.galleries.getList.html">flickr.galleries.getList</a>, sorted from newest to oldest, returning a list of gallery info blobs.</p> <p>Or you can fetch all the galleries a given photo is in with <a href="http://www.flickr.com/services/api/flickr.galleries.getListForPhoto.html">flickr.galleries.getListForPhoto</a>.</p> <h3 id="a_bag_of_photos">A Bag of Photos</h3> <p>Perhaps most interesting, <a href="http://www.flickr.com/services/api/flickr.galleries.getPhotos.html">flickr.galleries.getPhotos</a> will return a list of all the photos for a <a href="http://www.flickr.com/photos/heydrienne/galleries/72157616269927302/">given gallery</a>. It’s a <a href="http://code.flickr.com/blog/2008/08/19/standard-photos-response-apis-for-civilized-age/">standard photo response</a>, with a twist.</p> <pre><code><photos page="1" pages="1" perpage="500" total="15"> <photo id="2935475111" owner="8147452@N05" secret="e20746148b" server="3068" farm="4" title="Day off from the Death Star." ispublic="1" isfriend="0" isfamily="0" is_primary="1" has_comment="1"> <comment>best cat picture ever!</comment> </photo> <photo id="3078977730" owner="68779755@N00" secret="dba9d8105e" server="3229" farm="4" title="&quot;We could stuff it with Kleenex...&quot;" ispublic="1" isfriend="0" isfamily="0" is_primary="0" has_comment="0" /> <photo id="3212123792" owner="10983978@N03" secret="4231501383" server="3391" farm="4" title="1-19-09: Some People Just Don't Get It" ispublic="1" isfriend="0" isfamily="0" is_primary="0" has_comment="0" /> .... </photos> </code></pre> <p>In addition to standard photo response attributes, there is also a <code>has_comment</code> attribute which signals whether the gallery creator added a comment about why she included the photo, and whether the child <code>comment</code> element is present. Also <code>is_primary</code>, when set to 1, indicates this is the gallery’s “cover photo”.</p> <h3 id="crud">CRUD</h3> <p><a href="http://www.flickr.com/services/api/flickr.galleries.create.html">flickr.galleries.create</a> creates a gallery, with a title, description, and optional primary photo, and will return a <code>gallery</code> element with the compound-id and the URL of the gallery.</p> <pre><code><gallery id="50736-72157623680420409" url="http://www.flickr.com/photos/kellan/galleries/72157623680420409" /> </code></pre> <p><a href="http://www.flickr.com/services/api/flickr.galleries.editMeta.html">flickr.galleries.editMeta</a> is simply for updating the title and description. <a href="http://www.flickr.com/services/api/flickr.galleries.editPhoto.html">flickr.galleries.editPhoto</a> confusingly doesn’t edit a photo, but rather the comment about a photo in a gallery.</p> <p>Of course the money is all in <a href="http://www.flickr.com/services/api/flickr.galleries.addPhoto.html">flickr.galleries.addPhoto</a> which allows you to actually build a gallery of photos.</p> <p>Nota bene: Remember only <a href="http://www.flickr.com/help/galleries/#1051181">public-safe</a> can be added to galleries.</p> <h3 id="the_curated_life">The Curated Life</h3> <p>We’ve also added the ability to restrict searches to only photos in galleries, with the <code>in_gallery</code> argument to <a href="http://www.flickr.com/services/api/flickr.photos.search.html">flickr.photos.search</a></p> <p>So whether you’re interested in <a href="http://www.flickr.com/show.gne?api_method=flickr.photos.search&method_params=in_gallery|1;text|kittens">kittens deemed cute enough for galleries</a>, or <a href="http://www.flickr.com/show.gne?api_method=flickr.photos.search&method_params=in_gallery|1;text|pink">hand selected pink photos</a>, or <a href="http://www.flickr.com/show.gne?api_method=flickr.photos.search&method_params=in_gallery|1;is_commons|1">Flickr Commons photos in galleries</a>, or <a href="http://www.flickr.com/show.gne?api_method=flickr.photos.search&method_params=in_gallery|1;woe_id|12589335">simply photos taken near you (assuming you’re in Brooklyn), in galleries</a>, that’s available.</p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/galleries/" rel="tag">galleries</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-1324 --> <article id="post-957" class="post-957 post type-post status-publish format-standard hentry category-uncategorized tag-api tag-blogging tag-twitter"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2009/06/30/twitter-in-the-api/" rel="bookmark">Twitter in the API</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2009/06/30/twitter-in-the-api/" title="8:12 pm" rel="bookmark"><time class="entry-date" datetime="2009-06-30T20:12:23-07:00">June 30, 2009</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/flickrphotography/" title="View all posts by Kay Kremerskothen" rel="author">Kay Kremerskothen</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <div style="margin-top:20px;margin-bottom:30px;"> <a href="http://www.flickr.com/photos/87331483@N00/3486881225/" title="Mission 24: Yesterday by mocachip, on Flickr"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3612/3486881225_87f089526d.jpg" width="500" height="326" alt="Mission 24: Yesterday" style="border:1px dotted #ccc;padding:5px" /></a></p> <div style="text-align:right;font-size:small;margin-right:20px;"> <a href="http://www.flickr.com/photos/87331483@N00">photo by mocachip</a> </div> </div> <p>Ever since we launched our <a href="http://www.flickr.com/groups/flickrtwitterbeta/">Flickr2Twitter beta</a>, developers have been requesting new API methods, so they can support Flickr as a photo sharing option in their Twitter clients.</p> <p>I’ve got good news, and bad news.</p> <p>The bad news is we don’t have any new APIs to offer you.</p> <p>The good news is we shipped our “Twitter APIs” nearly five years ago.</p> <p>Let me explain.</p> <h3 id="working_with_blogs_including_twitter">Working with Blogs (including Twitter)</h3> <p>For as long as anyone can remember, we’ve supported the option of posting to external blogs directly from Flickr. Once you’ve configured a blogging service it becomes available in the “Blog This” drop down, as an option for Upload by Email, and, of course, in the API.</p> <p>You and I might have serious philosophical questions about whether Twitter is a blogging service, but our web servers are more pragmatic. To them, the Twitter integration is just a new blogging service.</p> <h3 id="configuring_a_blogging_service">Configuring a blogging service</h3> <p>The first step for a member wishing to blog (or tweet) via Flickr is to configure an external blog. The only way to do this on <a href="http://flickr.com">flickr.com</a>, generally from the <a href="http://www.flickr.com/account/blogs/add/">Add a blog</a> page.</p> <p>Twitter is a bit special (or rather a preview of things to come) as we’ve given it its own <a href="http://www.flickr.com/services/twitter/">service page</a>. Directing users of your app to the <a href="http://www.flickr.com/services/twitter/">Flickr2Twitter page</a> is probably the best way get them “tweet ready”.</p> <h3 id="all_ready">All set?</h3> <div style="margin-top:20px;margin-bottom:30px;"> <a href="http://www.flickr.com/photos/rckmsckm/2599195330/" title="THE FORTUNE WITHIN by RCKM ©®™, on Flickr"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3056/2599195330_c9e3a34126.jpg" width="500" height="333" alt="THE FORTUNE WITHIN" style="border:1px dotted #ccc;padding:5px" /></a></p> <div style="text-align:right;font-size:small;margin-right:20px;"> <a href="http://www.flickr.com/photos/rckmsckm">photo by RCKM ©®™</a> </div> </div> <p>From here on out, you’ll need your user to have authorized you to access their Flickr account. (<a href="http://www.flickr.com/services/api/misc.userauth.html">Find out more about FlickrAuth</a>)</p> <p>With a signed call to <a href="http://www.flickr.com/services/api/flickr.blogs.getList.html">flickr.blogs.getList()</a> you can get a list of all the blogging services a member has configured. Alternately you can pass in a <code>service</code> id (e.g. <code>Twitter</code>) to scope the list of blogs to the service you’re interested in. The response looks something like:</p> <pre><code><blogs> <blog id="7214" name="Code Flickr" service="MetaWeblogAPI" needspassword="0" url="http://code.flickr.com/blog/"/> <blog id="7215" name="Twitter: kellan" service="Twitter" needspassword="0" url="http://twitter.com/kellan"/> <blog id="72157" name="Twitter: Flickr" service="Twitter" needspassword="0" url="http://twitter.com/flickr"/> </blogs> </code></pre> <p>This account has 3 blogs configured. A <a href="http://wordpress.org">WordPress</a> blog, and two <a href="http://twitter.com">Twitter</a> accounts. Each one has a unique id. Additionally <code>needpassword="0"</code> means we have credentials for these blogs stored server side and you don’t need to prompt your user to log in to their blog.</p> <p>If you passed in <code>Twitter</code> as the service, and instead of the above you got something like:</p> <pre><code><blogs/> </code></pre> <p>Then your user hasn’t configured any blogs for that service.</p> <h3 id="the_easy_option_upload_a_photo_to_flickr_post_to_twitter_via_flickr">The Easy Option: Upload a photo to Flickr, post to Twitter via Flickr</h3> <p>If your application has been authorized to upload photos on your user’s behalf, and you’ve made sure they have a Twitter blog configured with Flickr, then the easiest solution is to use Flickr as a passthru service. </p> <p>Once you’ve successfully uploaded a photo you’ll get an API response like <code><photoid>1234</photoid></code>. (Find out <a href="http://www.flickr.com/services/api/upload.api.html">more about uploading</a> and <a href="http://www.flickr.com/services/api/upload.async.html">asynchronous uploading</a>). </p> <p>Pass the <code>blog id</code> from the <code><blogs></code> list above, and the <code>photoid</code> from the upload response to <a href="http://www.flickr.com/services/api/flickr.blogs.postPhoto.html">flickr.blogs.postPhoto()</a>. If you’re posting to Twitter the <code>title</code> argument is optional and the <code>description</code> argument is ignored. (By default the title of the photo is the body of the tweet, alternately pass a different status update in the <code>title</code> field)</p> <p>Or instead of passing a blog id, you can pass a service id (i.e. <code>Twitter</code>) and the photo (and blog post) will be sent to the first matching blog of that service. If we don’t find a blog matching that service, you’ll get a “<code>Blog not found.</code>” error.</p> <p>Assuming your API call to <a href="http://www.flickr.com/services/api/flickr.blogs.postPhoto.html">flickr.blogs.postPhoto()</a> is well formed, Flickr will turn around and post your user’s tweet to <a href="http://twitter.com">Twitter</a>, including a <a href="http://www.flickr.com/services/api/misc.urls.html#short">short flic.kr url</a> linking back to their photo.</p> <div style="margin-top:20px;margin-bottom:30px;"> <a href="http://www.flickr.com/photos/tonyadcock/3011228073/" title="I cant read, but the cookie is good by tonyadcockphotos, on Flickr"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3178/3011228073_e9f24ce543.jpg" width="500" height="400" alt="I cant read, but the cookie is good" style="border:1px dotted #ccc;padding:5px" /></a></p> <div style="text-align:right;font-size:small;margin-right:20px;"> <a href="http://www.flickr.com/photos/tonyadcock/">photo by tonyadcockphotos</a> </div> </div> <h3 id="the_established_option_upload_a_photo_flickr_post_to_twitter_any_which_way_you_can">The Established Option: Upload a photo Flickr, post to Twitter any which way you can</h3> <p>If you’re looking to integrate Flickr photos into an existing Twitter application you might already have a preferred method for posting to Twitter. </p> <p>After you’ve <a href="http://www.flickr.com/services/api/upload.api.html">successfully uploaded a photo</a> and received the <code>photoid</code> follow these <a href="http://www.flickr.com/services/api/misc.urls.html#short">instructions for manufacturing a short url</a> using the <a href="http://flic.kr">flic.kr</a> domain.</p> <p>Unlike most URL shortening schemes, every photo on Flickr already has a short URL associated with it. The follow the form:</p> <pre><code>http://flic.kr/p/{base58-photo-id} </code></pre> <p>By the way, you shouldn’t feel constrained to only use short urls on Twitter. They work equally well for a diverse range of applications including fortune cookies.</p> <h3 id="thumbnails">Thumbnails</h3> <p>If you want to display a thumbnail of a photo, you’ll need to make an API call to one of the methods that returns the photo’s secret. Either <a href="http://www.flickr.com/services/api/flickr.photos.getSizes.html"> flickr.photos.getSizes()</a> or <a href="http://www.flickr.com/services/api/flickr.photos.getInfo.html">flickr.photos.getInfo()</a> will do. Read up <a href="http://www.flickr.com/services/api/misc.urls.html">on constructing Flickr URLs</a>.</p> <h3 id="follow_along">Follow Along</h3> <p>My favorite new game has been watching the <a href="http://search.twitter.com/search?q=flic.kr+source%3AFlickr">flows of shared Flickr photos</a> as they appear on Twitter.</p> <p>Happy photo sharing!</p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/blogging/" rel="tag">blogging</a>, <a href="https://code.flickr.net/tag/twitter/" rel="tag">twitter</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-957 --> <article id="post-868" class="post-868 post type-post status-publish format-standard hentry category-uncategorized tag-api"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2009/04/20/what-would-brooklyn-do/" rel="bookmark">What Would Brooklyn Do?</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2009/04/20/what-would-brooklyn-do/" title="8:00 pm" rel="bookmark"><time class="entry-date" datetime="2009-04-20T20:00:02-07:00">April 20, 2009</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/flickrphotography/" title="View all posts by Kay Kremerskothen" rel="author">Kay Kremerskothen</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <div style="margin-bottom:30px;"> <a href="http://www.flickr.com/photos/katefranzman/3446758883/" title="MW2009 by kfranzman, on Flickr"><img loading="lazy" decoding="async" src="http://farm4.static.flickr.com/3363/3446758883_d00f670125.jpg" width="500" height="375" alt="MW2009" style="border:1px dotted #ccc;padding:10px;"/></a></p> <div style="font-size:11px;text-align:right;font-family:sans-serif;margin-right:15px;"><a href="http://www.flickr.com/photos/katefranzman/3446758883/in/pool-mw2009/">photo by kfranzman</a></div> </div> <p>The other day, Mike Ellis posted <a href="http://electronicmuseum.org.uk/2009/04/16/the-brooklyn-museum-api-qa-with-shelley-bernstein-and-paul-beaudoin/">a really lovely interview with Shelley Bernstein and Paul Beaudoin</a> about the release of the <a href="http://www.brooklynmuseum.org/community/blogosphere/bloggers/2009/03/04/brooklyn-museum-collection-api/">Brooklyn Museum’s Collections API</a>.</p> <p>One passage that I thought was worth calling out, and which I’ve copied verbatim below, is Shelley’s answer to the question “Why did you decide to build an API?”</p> <blockquote style="margin:40px;font-family:sans-serif;"> <p>First, practical… in the past we’d been asked to be a part of larger projects where institutions were trying to aggregate data across many collections (like d*hub). At the time, we couldn’t justify allocating the time to provide data sets which would become stale as fast as we could turn over the data. By developing the API, we can create this one thing that will work for many people so it no longer become a project every time we are asked to take part.</p> <p>Second, community… the developer community is not one we’d worked with before. <i>We’d recently had exposure to the <a href="http://www.indicommons.org/">indicommons community</a> at the Flickr Commons and had seen developers like David Wilkinson do <a href="http://www.indicommons.org/tools/">some great things</a> with our data there. It’s been a very positive experience and one we wanted to carry forward</i> (emphasis mine) into our Collection, not just the materials we are posting to The Commons.</p> <p>Third, community+practical… I think we needed to recognize that ideas about our data can come from anywhere, and encourage outside partnerships. We should recognize that programmers from outside the organization will have skills and ideas that we don’t have internally and encourage everyone to use them with our data if they want to. When they do, we want to make sure we get them the credit they deserve by pointing our visitors to their sites so they get some exposure for their efforts.</p> </blockquote> <p>The only thing I would add is: What she said!</p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/api/" rel="tag">api</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-868 --> <article id="post-777" class="post-777 post type-post status-publish format-standard hentry category-uncategorized tag-add-new-tag tag-api tag-astrometry tag-contest tag-flickr tag-google tag-machine-tags tag-science tag-space tag-tagging tag-yahoo tag-yql"> <header class="entry-header"> <h1 class="entry-title"><a href="https://code.flickr.net/2009/03/20/tags-in-space/" rel="bookmark">Tags in Space</a></h1> <div class="entry-meta"> <span class="sep">Posted on </span><a href="https://code.flickr.net/2009/03/20/tags-in-space/" title="8:49 pm" rel="bookmark"><time class="entry-date" datetime="2009-03-20T20:49:08-07:00">March 20, 2009</time></a><span class="by-author"> <span class="sep"> by </span> <span class="author vcard"><a class="url fn n" href="https://code.flickr.net/author/flickrphotography/" title="View all posts by Kay Kremerskothen" rel="author">Kay Kremerskothen</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p>A lot of you enjoyed our post (<a href="http://code.flickr.com/blog/2009/02/18/found-in-space/">“Found in Space”</a>) on the amazing <a href="http://astrometry.net/">astrometry.net</a> project, and there have been some interesting followups.</p> <p>A mysterious figure known only as “jim” paired up <a href="http://eatyourgreens.org.uk/testapps/yql/overlay2.html">astronomy photos from Flickr with Google Sky</a>. (You’re going to need the Google Earth plug-in for your browser — just follow the instructions on that page if you don’t have it.) In his <a href="http://eatyourgreens.org.uk/archives/2009/03/mapping-the-sky-with-yql-and-astrometrynet.html#more-300">technical writeup</a>, “jim” explains how he used the <a href="http://developer.yahoo.com/yql/">Yahoo Query Language</a> (YQL) to fetch the data. YQL is similar to the existing <a href="http://www.flickr.com/services/api/">Flickr APIs</a>, but it’s a query language like SQL rather than a set of REST-ish APIs. And both of those are really just ways to get data out of Flickr’s <a href="http://code.flickr.com/blog/2008/12/15/machine-tag-hierarchies/">machine tag</a> system, specifically the <a href="http://www.flickr.com/photos/tags/astro:*"><code>astro:*</code></a> namespace. It’s turtles all the way down.</p> <p>Who else is using astrotags? The British Royal Observatory in Greenwich is sponsoring a contest to determine the <a href="http://www.nmm.ac.uk/visit/exhibitions/astronomy-photographer-of-the-year/">Astronomy Photographer of the Year</a> and the whole thing is based on a <a href="http://www.nmm.ac.uk/visit/exhibitions/astronomy-photographer-of-the-year/flickr-group/">Flickr group</a> and extensive use of Flickr’s APIs. The integration is so seamless — galleries of photos and discussions are surfaced on their site as well as ours — you might as well consider Flickr to be their “backend” server. But they’ve also added much, such as great documentation about <a href="http://www.nmm.ac.uk/visit/exhibitions/astronomy-photographer-of-the-year/astrotags/">how to astrotag your photos</a> as well as <a href="http://www.nmm.ac.uk/visit/exhibitions/astronomy-photographer-of-the-year/astro-robot/">a concise explanation</a> about how Astrometry.net identifies your photo, even among millions of known stars. (The sci-fi website <a href="http://io9.com">io9</a> interviewed <a href="http://io9.com/5169232/the-robot-who-helps-astronomers-identify-stars">Fiona Romeo</a> of the Royal Observatory about the contest; check it out.) </p> <p>It’s dizzying how many services have been combined here — Astrometry.net grew out of research at the University of Toronto, web mashups use Google Sky for visualization in context, Yahoo infrastructure delivers and transforms data, the Royal Observatory at Greenwich provides leadership and expertise, and then little old Flickr acts as a data repository and social hub. And let’s not forget you, the Flickr community, and your inexhaustible creativity — which is the reason why all this can even come together. </p> <p>All this was done with pretty light coordination and few people at Flickr were even aware what was going on until recently. I have no idea what the future is for APIs and a web of services loosely joined, but I hope we get to see more and more of this sort of thing.</p> </div><!-- .entry-content --> <footer class="entry-meta"> <span class="cat-links"> <span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> <a href="https://code.flickr.net/category/uncategorized/" rel="category tag">Uncategorized</a> </span> <span class="sep"> | </span> <span class="tag-links"> <span class="entry-utility-prep entry-utility-prep-tag-links">Tagged</span> <a href="https://code.flickr.net/tag/add-new-tag/" rel="tag">Add new tag</a>, <a href="https://code.flickr.net/tag/api/" rel="tag">api</a>, <a href="https://code.flickr.net/tag/astrometry/" rel="tag">astrometry</a>, <a href="https://code.flickr.net/tag/contest/" rel="tag">contest</a>, <a href="https://code.flickr.net/tag/flickr/" rel="tag">flickr</a>, <a href="https://code.flickr.net/tag/google/" rel="tag">google</a>, <a href="https://code.flickr.net/tag/machine-tags/" rel="tag">machine tags</a>, <a href="https://code.flickr.net/tag/science/" rel="tag">science</a>, <a href="https://code.flickr.net/tag/space/" rel="tag">space</a>, <a href="https://code.flickr.net/tag/tagging/" rel="tag">tagging</a>, <a href="https://code.flickr.net/tag/yahoo/" rel="tag">yahoo</a>, <a href="https://code.flickr.net/tag/yql/" rel="tag">yql</a> </span> </footer><!-- .entry-meta --> </article><!-- #post-777 --> <nav id="nav-below"> <h3 class="assistive-text">Post navigation</h3> <div class="nav-previous"><a href="https://code.flickr.net/tag/api/page/2/" ><span class="meta-nav">←</span> Older posts</a></div> <div class="nav-next"></div> </nav><!-- #nav-above --> </div><!-- #content --> </section><!-- #primary --> <div id="secondary" class="widget-area" role="complementary"> <aside id="jetpack-search-filters-3" class="widget jetpack-filters widget_search"> <div id="jetpack-search-filters-3-wrapper" class="jetpack-instant-search-wrapper"> <div class="jetpack-search-form"> <form method="get" id="searchform" action="https://code.flickr.net/"> <label for="s" class="assistive-text">Search</label> <input type="text" class="field" name="s" id="s" placeholder="Search" /> <input type="submit" class="submit" name="submit" id="searchsubmit" value="Search" /> <input type="hidden" name="orderby" value="" /><input type="hidden" name="order" value="" /></form> </div> <h4 class="jetpack-search-filters-widget__sub-heading"> Categories </h4> <ul class="jetpack-search-filters-widget__filter-list"> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="category" data-val="uncategorized"> Uncategorized (136) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="category" data-val="geo"> geo (12) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="category" data-val="kittens"> kittens (10) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="category" data-val="change-log"> changelog (7) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="category" data-val="uploadr"> uploadr (6) </a> </li> </ul> <h4 class="jetpack-search-filters-widget__sub-heading"> Tags </h4> <ul class="jetpack-search-filters-widget__filter-list"> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="post_tag" data-val="api+api"> api (24) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="post_tag" data-val="api+geo"> geo (13) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="post_tag" data-val="api+machine-tags"> machine tags (10) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="post_tag" data-val="api+javascript"> javascript (9) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="taxonomy" data-taxonomy="post_tag" data-val="api+kittentuesday"> kittentuesday (8) </a> </li> </ul> <h4 class="jetpack-search-filters-widget__sub-heading"> Year </h4> <ul class="jetpack-search-filters-widget__filter-list"> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="year_post_date" data-val="2022-01-01 00:00:00" > 2022 (2) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="year_post_date" data-val="2021-01-01 00:00:00" > 2021 (1) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="year_post_date" data-val="2018-01-01 00:00:00" > 2018 (1) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="year_post_date" data-val="2017-01-01 00:00:00" > 2017 (2) </a> </li> <li> <a href="#" class="jetpack-search-filter__link" data-filter-type="year_post_date" data-val="2016-01-01 00:00:00" > 2016 (5) </a> </li> </ul> </div></aside> <aside id="recent-posts-2" class="widget widget_recent_entries"> <h3 class="widget-title">Recent Posts</h3> <ul> <li> <a href="https://code.flickr.net/2022/02/14/safer-internet-day-and-open-source-codes-of-conduct/">Safer Internet Day and Open Source Codes of Conduct</a> </li> <li> <a href="https://code.flickr.net/2022/01/04/a-pluggable-solution-for-api-observability-on-our-php-system/">A Pluggable Solution for API Observability on our PHP System</a> </li> <li> <a href="https://code.flickr.net/2021/11/22/flickr-engineering-team-vision-guiding-principles/">Flickr Engineering Team Vision & Guiding Principles</a> </li> <li> <a href="https://code.flickr.net/2018/04/20/together/">Together</a> </li> <li> <a href="https://code.flickr.net/2017/03/07/introducing-similarity-search-at-flickr/">Introducing Similarity Search at Flickr</a> </li> </ul> </aside><aside id="archives-2" class="widget widget_archive"><h3 class="widget-title">Archives</h3> <ul> <li><a href='https://code.flickr.net/2022/02/'>February 2022</a></li> <li><a href='https://code.flickr.net/2022/01/'>January 2022</a></li> <li><a href='https://code.flickr.net/2021/11/'>November 2021</a></li> <li><a href='https://code.flickr.net/2018/04/'>April 2018</a></li> <li><a href='https://code.flickr.net/2017/03/'>March 2017</a></li> <li><a href='https://code.flickr.net/2017/01/'>January 2017</a></li> <li><a href='https://code.flickr.net/2016/09/'>September 2016</a></li> <li><a href='https://code.flickr.net/2016/05/'>May 2016</a></li> <li><a href='https://code.flickr.net/2016/04/'>April 2016</a></li> <li><a href='https://code.flickr.net/2016/03/'>March 2016</a></li> <li><a href='https://code.flickr.net/2015/12/'>December 2015</a></li> <li><a href='https://code.flickr.net/2015/11/'>November 2015</a></li> <li><a href='https://code.flickr.net/2015/09/'>September 2015</a></li> <li><a href='https://code.flickr.net/2015/07/'>July 2015</a></li> <li><a href='https://code.flickr.net/2015/06/'>June 2015</a></li> <li><a href='https://code.flickr.net/2015/03/'>March 2015</a></li> <li><a href='https://code.flickr.net/2014/10/'>October 2014</a></li> <li><a href='https://code.flickr.net/2014/08/'>August 2014</a></li> <li><a href='https://code.flickr.net/2014/07/'>July 2014</a></li> <li><a href='https://code.flickr.net/2014/05/'>May 2014</a></li> <li><a href='https://code.flickr.net/2014/04/'>April 2014</a></li> <li><a href='https://code.flickr.net/2014/02/'>February 2014</a></li> <li><a href='https://code.flickr.net/2013/09/'>September 2013</a></li> <li><a href='https://code.flickr.net/2013/06/'>June 2013</a></li> <li><a href='https://code.flickr.net/2013/03/'>March 2013</a></li> <li><a href='https://code.flickr.net/2012/12/'>December 2012</a></li> <li><a href='https://code.flickr.net/2012/10/'>October 2012</a></li> <li><a href='https://code.flickr.net/2012/07/'>July 2012</a></li> <li><a href='https://code.flickr.net/2012/06/'>June 2012</a></li> <li><a href='https://code.flickr.net/2012/05/'>May 2012</a></li> <li><a href='https://code.flickr.net/2012/04/'>April 2012</a></li> <li><a href='https://code.flickr.net/2012/02/'>February 2012</a></li> <li><a href='https://code.flickr.net/2012/01/'>January 2012</a></li> <li><a href='https://code.flickr.net/2011/12/'>December 2011</a></li> <li><a href='https://code.flickr.net/2011/10/'>October 2011</a></li> <li><a href='https://code.flickr.net/2011/09/'>September 2011</a></li> <li><a href='https://code.flickr.net/2011/08/'>August 2011</a></li> <li><a href='https://code.flickr.net/2011/07/'>July 2011</a></li> <li><a href='https://code.flickr.net/2011/06/'>June 2011</a></li> <li><a href='https://code.flickr.net/2011/03/'>March 2011</a></li> <li><a href='https://code.flickr.net/2011/02/'>February 2011</a></li> <li><a href='https://code.flickr.net/2011/01/'>January 2011</a></li> <li><a href='https://code.flickr.net/2010/11/'>November 2010</a></li> <li><a href='https://code.flickr.net/2010/10/'>October 2010</a></li> <li><a href='https://code.flickr.net/2010/09/'>September 2010</a></li> <li><a href='https://code.flickr.net/2010/08/'>August 2010</a></li> <li><a href='https://code.flickr.net/2010/07/'>July 2010</a></li> <li><a href='https://code.flickr.net/2010/05/'>May 2010</a></li> <li><a href='https://code.flickr.net/2010/04/'>April 2010</a></li> <li><a href='https://code.flickr.net/2010/03/'>March 2010</a></li> <li><a href='https://code.flickr.net/2010/02/'>February 2010</a></li> <li><a href='https://code.flickr.net/2010/01/'>January 2010</a></li> <li><a href='https://code.flickr.net/2009/12/'>December 2009</a></li> <li><a href='https://code.flickr.net/2009/11/'>November 2009</a></li> <li><a href='https://code.flickr.net/2009/10/'>October 2009</a></li> <li><a href='https://code.flickr.net/2009/09/'>September 2009</a></li> <li><a href='https://code.flickr.net/2009/07/'>July 2009</a></li> <li><a href='https://code.flickr.net/2009/06/'>June 2009</a></li> <li><a href='https://code.flickr.net/2009/05/'>May 2009</a></li> <li><a href='https://code.flickr.net/2009/04/'>April 2009</a></li> <li><a href='https://code.flickr.net/2009/03/'>March 2009</a></li> <li><a href='https://code.flickr.net/2009/02/'>February 2009</a></li> <li><a href='https://code.flickr.net/2009/01/'>January 2009</a></li> <li><a href='https://code.flickr.net/2008/12/'>December 2008</a></li> <li><a href='https://code.flickr.net/2008/11/'>November 2008</a></li> <li><a href='https://code.flickr.net/2008/10/'>October 2008</a></li> <li><a href='https://code.flickr.net/2008/09/'>September 2008</a></li> <li><a href='https://code.flickr.net/2008/08/'>August 2008</a></li> <li><a href='https://code.flickr.net/2008/07/'>July 2008</a></li> <li><a href='https://code.flickr.net/2008/06/'>June 2008</a></li> <li><a href='https://code.flickr.net/2008/05/'>May 2008</a></li> <li><a href='https://code.flickr.net/2008/04/'>April 2008</a></li> </ul> </aside><aside id="categories-2" class="widget widget_categories"><h3 class="widget-title">Categories</h3> <ul> <li class="cat-item cat-item-11749740"><a href="https://code.flickr.net/category/api-2/">API</a> </li> <li class="cat-item cat-item-564792"><a href="https://code.flickr.net/category/change-log/">changelog</a> </li> <li class="cat-item cat-item-5784"><a href="https://code.flickr.net/category/event/">event</a> </li> <li class="cat-item cat-item-29160"><a href="https://code.flickr.net/category/geo/">geo</a> </li> <li class="cat-item cat-item-139037766"><a href="https://code.flickr.net/category/hadoop/">hadoop</a> </li> <li class="cat-item cat-item-32"><a href="https://code.flickr.net/category/infrastructure/">infrastructure</a> </li> <li class="cat-item cat-item-139037765"><a href="https://code.flickr.net/category/kittens/">kittens</a> </li> <li class="cat-item cat-item-20156"><a href="https://code.flickr.net/category/labs/">labs</a> </li> <li class="cat-item cat-item-171"><a href="https://code.flickr.net/category/meta/">meta</a> </li> <li class="cat-item cat-item-7092"><a href="https://code.flickr.net/category/metrics/">metrics</a> </li> <li class="cat-item cat-item-139037764"><a href="https://code.flickr.net/category/open-source/">open source</a> </li> <li class="cat-item cat-item-1930"><a href="https://code.flickr.net/category/performance/">performance</a> </li> <li class="cat-item cat-item-304"><a href="https://code.flickr.net/category/photos/">photos</a> </li> <li class="cat-item cat-item-2373"><a href="https://code.flickr.net/category/search/">search</a> </li> <li class="cat-item cat-item-1"><a href="https://code.flickr.net/category/uncategorized/">Uncategorized</a> </li> <li class="cat-item cat-item-249276"><a href="https://code.flickr.net/category/uploadr/">uploadr</a> </li> <li class="cat-item cat-item-412"><a href="https://code.flickr.net/category/video/">video</a> </li> <li class="cat-item cat-item-830560"><a href="https://code.flickr.net/category/xulrunner/">xulrunner</a> </li> </ul> </aside><aside id="meta-2" class="widget widget_meta"><h3 class="widget-title">Meta</h3> <ul> <li><a href="https://code.flickr.net/wp-login.php">Log in</a></li> <li><a href="https://code.flickr.net/feed/">Entries feed</a></li> <li><a href="https://code.flickr.net/comments/feed/">Comments feed</a></li> <li><a href="https://wordpress.org/">WordPress.org</a></li> </ul> </aside> </div><!-- #secondary .widget-area --> </div><!-- #main --> <footer id="colophon" role="contentinfo"> <div id="site-generator"> © 2025 Flickr, Inc. All rights reserved. | Powered by <a href="https://wpvip.com/?utm_source=vip_powered_wpcom&utm_medium=web&utm_campaign=VIP%20Footer%20Credit&utm_term=code.flickr.net" rel="generator nofollow" class="powered-by-wpcom">WordPress VIP</a> </div> </footer><!-- #colophon --> </div><!-- #page --> <div class="jetpack-instant-search__widget-area" style="display: none"> <div id="jetpack-search-filters-2" class="widget jetpack-filters widget_search"> <div id="jetpack-search-filters-2-wrapper" class="jetpack-instant-search-wrapper"> </div></div> </div> <script type="text/javascript" src="https://code.flickr.net/_static/??-eJzTLy/QTc7PK0nNK9EvyClNz8wr1i+uzCtJrMjITM/IAeKS1CJMEWP94uSizIISoOIM5/yiVL2sYh19yo1yKiotzojIzQEaZ59ra2huYmBiaGxhaZgFAJAmP7Q=" ></script><script type='text/javascript'> (function(){ var corecss = document.createElement('link'); var themecss = document.createElement('link'); var corecssurl = "https://code.flickr.net/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/styles/shCore.css?ver=3.0.9b"; if ( corecss.setAttribute ) { corecss.setAttribute( "rel", "stylesheet" ); corecss.setAttribute( "type", "text/css" ); corecss.setAttribute( "href", corecssurl ); } else { corecss.rel = "stylesheet"; corecss.href = corecssurl; } document.head.appendChild( corecss ); var themecssurl = "https://code.flickr.net/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/styles/shThemeDefault.css?ver=3.0.9b"; if ( themecss.setAttribute ) { themecss.setAttribute( "rel", "stylesheet" ); themecss.setAttribute( "type", "text/css" ); themecss.setAttribute( "href", themecssurl ); } else { themecss.rel = "stylesheet"; themecss.href = themecssurl; } document.head.appendChild( themecss ); })(); SyntaxHighlighter.config.strings.expandSource = '+ expand source'; SyntaxHighlighter.config.strings.help = '?'; SyntaxHighlighter.config.strings.alert = 'SyntaxHighlighter\n\n'; SyntaxHighlighter.config.strings.noBrush = 'Can\'t find brush for: '; SyntaxHighlighter.config.strings.brushNotHtmlScript = 'Brush wasn\'t configured for html-script option: '; SyntaxHighlighter.defaults['pad-line-numbers'] = false; SyntaxHighlighter.defaults['toolbar'] = false; SyntaxHighlighter.all(); // Infinite scroll support if ( typeof( jQuery ) !== 'undefined' ) { jQuery( function( $ ) { $( document.body ).on( 'post-load', function() { SyntaxHighlighter.highlight(); } ); } ); } </script> <script type="text/javascript" src="https://code.flickr.net/wp-includes/js/dist/hooks.min.js?m=1742994401g" ></script><script type="text/javascript" src="https://code.flickr.net/wp-includes/js/dist/i18n.min.js?ver=5e580eb46a90c2b997e6" id="wp-i18n-js"></script> <script type="text/javascript" id="wp-i18n-js-after"> /* <![CDATA[ */ wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ 'ltr' ] } ); /* ]]> */ </script> <script type="text/javascript" src="https://code.flickr.net/wp-content/mu-plugins/jetpack-14.4/jetpack_vendor/automattic/jetpack-assets/build/i18n-loader.js?minify=true&ver=becd7d9884bc1b331e45" id="wp-jp-i18n-loader-js"></script> <script type="text/javascript" id="wp-jp-i18n-loader-js-after"> /* <![CDATA[ */ wp.jpI18nLoader.state = {"baseUrl":"https://code.flickr.net/wp-content/languages/","locale":"en_US","domainMap":{"jetpack-admin-ui":"plugins/jetpack","jetpack-assets":"plugins/jetpack","jetpack-backup-pkg":"plugins/jetpack","jetpack-blaze":"plugins/jetpack","jetpack-boost-core":"plugins/jetpack","jetpack-boost-speed-score":"plugins/jetpack","jetpack-classic-theme-helper":"plugins/jetpack","jetpack-compat":"plugins/jetpack","jetpack-config":"plugins/jetpack","jetpack-connection":"plugins/jetpack","jetpack-explat":"plugins/jetpack","jetpack-external-media":"plugins/jetpack","jetpack-forms":"plugins/jetpack","jetpack-image-cdn":"plugins/jetpack","jetpack-import":"plugins/jetpack","jetpack-ip":"plugins/jetpack","jetpack-jitm":"plugins/jetpack","jetpack-licensing":"plugins/jetpack","jetpack-masterbar":"plugins/jetpack","jetpack-my-jetpack":"plugins/jetpack","jetpack-password-checker":"plugins/jetpack","jetpack-plugins-installer":"plugins/jetpack","jetpack-post-list":"plugins/jetpack","jetpack-protect-models":"plugins/jetpack","jetpack-protect-status":"plugins/jetpack","jetpack-publicize-pkg":"plugins/jetpack","jetpack-search-pkg":"plugins/jetpack","jetpack-stats":"plugins/jetpack","jetpack-stats-admin":"plugins/jetpack","jetpack-sync":"plugins/jetpack","jetpack-videopress-pkg":"plugins/jetpack","jetpack-waf":"plugins/jetpack","jetpack-wordads":"plugins/jetpack","woocommerce-analytics":"plugins/jetpack"},"domainPaths":{"jetpack-admin-ui":"jetpack_vendor/automattic/jetpack-admin-ui/","jetpack-assets":"jetpack_vendor/automattic/jetpack-assets/","jetpack-backup-pkg":"jetpack_vendor/automattic/jetpack-backup/","jetpack-blaze":"jetpack_vendor/automattic/jetpack-blaze/","jetpack-boost-core":"jetpack_vendor/automattic/jetpack-boost-core/","jetpack-boost-speed-score":"jetpack_vendor/automattic/jetpack-boost-speed-score/","jetpack-classic-theme-helper":"jetpack_vendor/automattic/jetpack-classic-theme-helper/","jetpack-compat":"jetpack_vendor/automattic/jetpack-compat/","jetpack-config":"jetpack_vendor/automattic/jetpack-config/","jetpack-connection":"jetpack_vendor/automattic/jetpack-connection/","jetpack-explat":"jetpack_vendor/automattic/jetpack-explat/","jetpack-external-media":"jetpack_vendor/automattic/jetpack-external-media/","jetpack-forms":"jetpack_vendor/automattic/jetpack-forms/","jetpack-image-cdn":"jetpack_vendor/automattic/jetpack-image-cdn/","jetpack-import":"jetpack_vendor/automattic/jetpack-import/","jetpack-ip":"jetpack_vendor/automattic/jetpack-ip/","jetpack-jitm":"jetpack_vendor/automattic/jetpack-jitm/","jetpack-licensing":"jetpack_vendor/automattic/jetpack-licensing/","jetpack-masterbar":"jetpack_vendor/automattic/jetpack-masterbar/","jetpack-my-jetpack":"jetpack_vendor/automattic/jetpack-my-jetpack/","jetpack-password-checker":"jetpack_vendor/automattic/jetpack-password-checker/","jetpack-plugins-installer":"jetpack_vendor/automattic/jetpack-plugins-installer/","jetpack-post-list":"jetpack_vendor/automattic/jetpack-post-list/","jetpack-protect-models":"jetpack_vendor/automattic/jetpack-protect-models/","jetpack-protect-status":"jetpack_vendor/automattic/jetpack-protect-status/","jetpack-publicize-pkg":"jetpack_vendor/automattic/jetpack-publicize/","jetpack-search-pkg":"jetpack_vendor/automattic/jetpack-search/","jetpack-stats":"jetpack_vendor/automattic/jetpack-stats/","jetpack-stats-admin":"jetpack_vendor/automattic/jetpack-stats-admin/","jetpack-sync":"jetpack_vendor/automattic/jetpack-sync/","jetpack-videopress-pkg":"jetpack_vendor/automattic/jetpack-videopress/","jetpack-waf":"jetpack_vendor/automattic/jetpack-waf/","jetpack-wordads":"jetpack_vendor/automattic/jetpack-wordads/","woocommerce-analytics":"jetpack_vendor/automattic/woocommerce-analytics/"}}; /* ]]> */ </script> <script type="text/javascript" src="https://code.flickr.net/_static/??/wp-includes/js/dist/vendor/wp-polyfill.min.js,/wp-includes/js/dist/url.min.js?m=1742994401j" ></script><script type="text/javascript" id="jetpack-instant-search-js-before"> /* <![CDATA[ */ var JetpackInstantSearchOptions=JSON.parse(decodeURIComponent("%7B%22overlayOptions%22%3A%7B%22colorTheme%22%3A%22light%22%2C%22enableInfScroll%22%3Atrue%2C%22enableFilteringOpensOverlay%22%3Atrue%2C%22enablePostDate%22%3Atrue%2C%22enableSort%22%3Atrue%2C%22highlightColor%22%3A%22%23FFC%22%2C%22overlayTrigger%22%3A%22submit%22%2C%22resultFormat%22%3A%22expanded%22%2C%22showPoweredBy%22%3Atrue%2C%22defaultSort%22%3A%22relevance%22%2C%22excludedPostTypes%22%3A%5B%5D%7D%2C%22homeUrl%22%3A%22https%3A%5C%2F%5C%2Fcode.flickr.net%22%2C%22locale%22%3A%22en-US%22%2C%22postsPerPage%22%3A10%2C%22siteId%22%3A185426273%2C%22postTypes%22%3A%7B%22post%22%3A%7B%22singular_name%22%3A%22Post%22%2C%22name%22%3A%22Posts%22%7D%2C%22page%22%3A%7B%22singular_name%22%3A%22Page%22%2C%22name%22%3A%22Pages%22%7D%2C%22attachment%22%3A%7B%22singular_name%22%3A%22Media%22%2C%22name%22%3A%22Media%22%7D%7D%2C%22webpackPublicPath%22%3A%22https%3A%5C%2F%5C%2Fcode.flickr.net%5C%2Fwp-content%5C%2Fmu-plugins%5C%2Fjetpack-14.4%5C%2Fjetpack_vendor%5C%2Fautomattic%5C%2Fjetpack-search%5C%2Fbuild%5C%2Finstant-search%5C%2F%22%2C%22isPhotonEnabled%22%3Afalse%2C%22isFreePlan%22%3Afalse%2C%22apiRoot%22%3A%22https%3A%5C%2F%5C%2Fcode.flickr.net%5C%2Fwp-json%5C%2F%22%2C%22apiNonce%22%3A%22cb30975420%22%2C%22isPrivateSite%22%3Afalse%2C%22isWpcom%22%3Afalse%2C%22hasOverlayWidgets%22%3Atrue%2C%22widgets%22%3A%5B%7B%22filters%22%3A%5B%7B%22name%22%3A%22Bylines%22%2C%22type%22%3A%22taxonomy%22%2C%22taxonomy%22%3A%22byline%22%2C%22count%22%3A5%2C%22widget_id%22%3A%22jetpack-search-filters-2%22%2C%22filter_id%22%3A%22taxonomy_0%22%7D%2C%7B%22name%22%3A%22Categories%22%2C%22type%22%3A%22taxonomy%22%2C%22taxonomy%22%3A%22category%22%2C%22count%22%3A5%2C%22widget_id%22%3A%22jetpack-search-filters-2%22%2C%22filter_id%22%3A%22taxonomy_1%22%7D%2C%7B%22name%22%3A%22Tags%22%2C%22type%22%3A%22taxonomy%22%2C%22taxonomy%22%3A%22post_tag%22%2C%22count%22%3A5%2C%22widget_id%22%3A%22jetpack-search-filters-2%22%2C%22filter_id%22%3A%22taxonomy_2%22%7D%2C%7B%22name%22%3A%22Year%22%2C%22type%22%3A%22date_histogram%22%2C%22count%22%3A5%2C%22field%22%3A%22post_date%22%2C%22interval%22%3A%22year%22%2C%22widget_id%22%3A%22jetpack-search-filters-2%22%2C%22filter_id%22%3A%22date_histogram_3%22%7D%5D%2C%22widget_id%22%3A%22jetpack-search-filters-2%22%7D%5D%2C%22widgetsOutsideOverlay%22%3A%5B%7B%22filters%22%3A%5B%7B%22name%22%3A%22Bylines%22%2C%22type%22%3A%22taxonomy%22%2C%22taxonomy%22%3A%22byline%22%2C%22count%22%3A5%2C%22widget_id%22%3A%22jetpack-search-filters-3%22%2C%22filter_id%22%3A%22taxonomy_4%22%7D%2C%7B%22name%22%3A%22Categories%22%2C%22type%22%3A%22taxonomy%22%2C%22taxonomy%22%3A%22category%22%2C%22count%22%3A5%2C%22widget_id%22%3A%22jetpack-search-filters-3%22%2C%22filter_id%22%3A%22taxonomy_5%22%7D%2C%7B%22name%22%3A%22Tags%22%2C%22type%22%3A%22taxonomy%22%2C%22taxonomy%22%3A%22post_tag%22%2C%22count%22%3A5%2C%22widget_id%22%3A%22jetpack-search-filters-3%22%2C%22filter_id%22%3A%22taxonomy_6%22%7D%2C%7B%22name%22%3A%22Year%22%2C%22type%22%3A%22date_histogram%22%2C%22count%22%3A5%2C%22field%22%3A%22post_date%22%2C%22interval%22%3A%22year%22%2C%22widget_id%22%3A%22jetpack-search-filters-3%22%2C%22filter_id%22%3A%22date_histogram_7%22%7D%5D%2C%22widget_id%22%3A%22jetpack-search-filters-3%22%7D%5D%2C%22hasNonSearchWidgets%22%3Afalse%2C%22preventTrackingCookiesReset%22%3Afalse%7D")); /* ]]> */ </script> <script type="text/javascript" src="https://code.flickr.net/wp-content/mu-plugins/jetpack-14.4/jetpack_vendor/automattic/jetpack-search/build/instant-search/jp-search.js?minify=false&ver=749aa845a5ccb16bc8a6" id="jetpack-instant-search-js"></script> <script type="text/javascript" src="//stats.wp.com/w.js?ver=202515" id="jp-tracks-js"></script> <script type="text/javascript" id="jetpack-stats-js-before"> /* <![CDATA[ */ _stq = window._stq || []; _stq.push([ "view", JSON.parse("{\"v\":\"ext\",\"blog\":\"185426273\",\"post\":\"0\",\"tz\":\"-7\",\"srv\":\"code.flickr.net\",\"hp\":\"vip\",\"j\":\"1:14.4.1\"}") ]); _stq.push([ "clickTrackerInit", "185426273", "0" ]); /* ]]> */ </script> <script type="text/javascript" src="https://stats.wp.com/e-202515.js" id="jetpack-stats-js" defer="defer" data-wp-strategy="defer"></script> <script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script> </body> </html>