<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jan Vlnas</title>
    <description>The latest articles on DEV Community by Jan Vlnas (@jnv).</description>
    <link>https://dev.to/jnv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1363%2Fc509f283-5020-499d-b2d4-ea81309b8f43.jpg</url>
      <title>DEV Community: Jan Vlnas</title>
      <link>https://dev.to/jnv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jnv"/>
    <language>en</language>
    <item>
      <title>Get started with Greenhouse APIs: Overview and authentication</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Thu, 11 May 2023 12:12:08 +0000</pubDate>
      <link>https://dev.to/superface/get-started-with-greenhouse-apis-overview-and-authentication-2ip0</link>
      <guid>https://dev.to/superface/get-started-with-greenhouse-apis-overview-and-authentication-2ip0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Our goal at Superface is to simplify API integrations, so you can focus on building your app instead of reading the API docs. We’ve built &lt;a href="https://superface.ai/provider/greenhouse?utm_source=dev.to&amp;amp;utm_campaign=greenhouse-recruiting-api-overview-auth"&gt;ready-made integrations for Greenhouse&lt;/a&gt;, and in this article we’re sharing what we’ve learned through the process.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first step to integrating any API is obtaining access. When it comes to Applicant Tracking Systems (ATS), the API for &lt;a href="https://www.greenhouse.com/" rel="noopener noreferrer"&gt;Greenhouse&lt;/a&gt; is among the most developer-friendly, with useful and straightforward &lt;a href="https://developers.greenhouse.io/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Still, there are six separate APIs for Greenhouse, some of them with overlapping concerns (such as Harvest and Job Board APIs). We have prepared a little crash course on Greenhouse APIs with pointers on when to use which one, how to obtain credentials for each, and how to authenticate your API calls. You will also find examples in JavaScript, which should work in all modern runtimes (particularly Node.js version 18 and newer and Deno).&lt;/p&gt;

&lt;p&gt;This article focuses on Greenhouse Recruiting APIs. There are also APIs for &lt;a href="https://developers.greenhouse.io/gho.html" rel="noopener noreferrer"&gt;Greenhouse Onboarding&lt;/a&gt; and &lt;a href="https://developers.greenhouse.io/assessment.html" rel="noopener noreferrer"&gt;Assessment&lt;/a&gt;. We will cover these in future posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Job Board API is intended for building custom careers pages.

&lt;ul&gt;
&lt;li&gt;It’s publicly accessible without authentication, cached and not rate limited.&lt;/li&gt;
&lt;li&gt;It only uses HTTP Basic authentication for submitting candidates through the API.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The Harvest API provides full access to Greenhouse Recruiting data.

&lt;ul&gt;
&lt;li&gt;It’s useful for building advanced automation and internal productivity tools.&lt;/li&gt;
&lt;li&gt;It requires HTTP Basic authentication using API keys with granular permissions, and an &lt;code&gt;On-Behalf-Of&lt;/code&gt; header for auditing of write operations.&lt;/li&gt;
&lt;li&gt;The number of requests is limited within 10-second windows.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The Candidate Ingestion API is intended for recruiting partners, like agencies and job portals.

&lt;ul&gt;
&lt;li&gt;Access is authenticated either with HTTP Basic authentication (with API key provided by Greenhouse customer), or OAuth 2.0 with granular scopes.&lt;/li&gt;
&lt;li&gt;Requests authenticated by HTTP Basic require an &lt;code&gt;On-Behalf-Of&lt;/code&gt; header, which identifies the Greenhouse user and sets the integration’s permissions.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Job Board API for custom careers pages
&lt;/h2&gt;

&lt;p&gt;You can use the &lt;a href="https://developers.greenhouse.io/job-board.html" rel="noopener noreferrer"&gt;Job Board API&lt;/a&gt; to build a custom careers page for your company. It provides data about published jobs and the company hierarchy (offices and departments). Since job boards only work with published and public data, you don’t need any special credentials for read access – only a “board token” which &lt;a href="https://support.greenhouse.io/hc/en-us/articles/360020776251-Job-board-URL-for-Greenhouse-hosted-job-board" rel="noopener noreferrer"&gt;corresponds to the URL path of a job board&lt;/a&gt; (and can be &lt;a href="https://support.greenhouse.io/hc/en-us/articles/5888210160155-Find-your-job-board-URL-for-an-integration" rel="noopener noreferrer"&gt;customized&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For example, if the job board is accessible on the URL &lt;code&gt;https://boards.greenhouse.io/acme&lt;/code&gt;, the &lt;code&gt;board_token&lt;/code&gt; is &lt;code&gt;acme&lt;/code&gt;. Anyone can access the respective API endpoints, for example &lt;code&gt;https://boards-api.greenhouse.io/v1/boards/acme/jobs&lt;/code&gt; to &lt;a href="https://developers.greenhouse.io/job-board.html#jobs" rel="noopener noreferrer"&gt;list published jobs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The only exception is &lt;a href="https://developers.greenhouse.io/job-board.html#submit-an-application" rel="noopener noreferrer"&gt;posting job applications&lt;/a&gt;, which requires an API key. Greenhouse recommends using their embedded application form, but if you decide to build one on your own, you will need to &lt;a href="https://support.greenhouse.io/hc/en-us/articles/13446638483355-Create-a-job-board-API-key-for-an-integration" rel="noopener noreferrer"&gt;create a Job Board API key&lt;/a&gt;, which you’ll use in HTTP Basic authentication as the username, with no password. For example, in JavaScript with Node.js (v18+) or Deno, you can submit an application as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set according to your Job Board settings&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;JOB_BOARD_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`acmeinc`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Create Job Board key in Configure &amp;gt; Dev Center &amp;gt; API Credentials&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GREENHOUSE_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`2f11da80ea73b20b4d15bfab0ee73257-1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ID of the job where the application is submitted&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;JOB_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4043584006&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CANDIDATE_DATA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;j.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12345678&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data_compliance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;gdpr_consent_given&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// base64 encode credentials&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GREENHOUSE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`https://boards-api.greenhouse.io/v1/boards/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JOB_BOARD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JOB_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CANDIDATE_DATA&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Unexpected response from the server: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Application submitted!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error submitting application:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Harvest API for full access to recruiting data
&lt;/h2&gt;

&lt;p&gt;A step above the public Job Board API is the &lt;a href="https://developers.greenhouse.io/harvest.html" rel="noopener noreferrer"&gt;Harvest API&lt;/a&gt;, which provides access to “most” Greenhouse Recruiting data. This API gives you full access to read and modify candidates, interviews, jobs, and various other organization’s resources.&lt;/p&gt;

&lt;p&gt;Harvest API keys have granular permissions. You can &lt;a href="https://support.greenhouse.io/hc/en-us/articles/115000521723-Manage-Harvest-API-key-permissions" rel="noopener noreferrer"&gt;choose what API endpoints and operations are available&lt;/a&gt;, so you can limit the access scope (and potential security risks) of your application.&lt;/p&gt;

&lt;p&gt;Similarly to the Job Board API, Harvest API keys are used with HTTP Basic authentication, where the username is the API key and the password remains empty.&lt;/p&gt;

&lt;p&gt;In the following JavaScript example, we’re listing all candidates from the Harvest API using the &lt;a href="https://developers.greenhouse.io/harvest.html#get-list-candidates" rel="noopener noreferrer"&gt;GET Candidates endpoint&lt;/a&gt;. The pagination is handled through an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of" rel="noopener noreferrer"&gt;async iterable&lt;/a&gt;. Each API response &lt;a href="https://developers.greenhouse.io/harvest.html#pagination" rel="noopener noreferrer"&gt;has a Link header for navigating to the next page of results&lt;/a&gt;, so we’re parsing the header in the sample code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create Harvest API key in Configure &amp;gt; Dev Center &amp;gt; API Credentials&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GREENHOUSE_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`c8ee19deadbeef5466d92e643916316-2`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// base64 encode credentials&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GREENHOUSE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Parse Link header:&lt;/span&gt;
&lt;span class="c1"&gt;// Example: &amp;lt;https://harvest.greenhouse.io/v1/candidates?page=2&amp;amp;per_page=2&amp;gt;; rel="next", &amp;lt;https://harvest.greenhouse.io/v1/candidates?page=474&amp;amp;per_page=2&amp;gt;; rel="last"&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getNextPageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;linkHeader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;linkHeader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;linkHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rel="next"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;nextLink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;/&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;fetchCandidatesPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// TIP: add ?per_page=1 to test iteration or ?per_page=500 to get a maximum of results&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nextPageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://harvest.greenhouse.io/v1/candidates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextPageUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextPageUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;nextPageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getNextPageUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextPageUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unexpected response from the server: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;iterateCandidates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;fetchCandidatesPage&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`### Listing candidates, page &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;iterateCandidates&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the API key is global, there is no straightforward way to identify who performed which operations through the API. Therefore, for auditing purposes, write operations (creating, updating, and deleting resources) require an &lt;code&gt;On-Behalf-Of&lt;/code&gt; HTTP header containing the Greenhouse ID of the user performing the operation. Your application can get the ID of the user from the &lt;a href="https://developers.greenhouse.io/harvest.html#get-list-users" rel="noopener noreferrer"&gt;&lt;code&gt;GET List Users&lt;/code&gt; endpoint&lt;/a&gt;. If you know the email of the user logged into your app, you can use the &lt;code&gt;email&lt;/code&gt; query parameter to obtain the user’s Greenhouse record, including their ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Job Board API vs. Harvest API
&lt;/h2&gt;

&lt;p&gt;The Job Board API is clearly a subset of the Harvest API, so you may be asking why should you deal with the Job Board API at all?&lt;/p&gt;

&lt;p&gt;This depends on the type of application you’re building. The Job Boards API is mostly useful for custom career sites, so if you’re building that, consider the following advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Since the Job Board API is limited only to public data such as published job posts, there’s no risk of leaking sensitive information (for example if the API key is compromised).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limits:&lt;/strong&gt; The Job Board API is heavily cached and there are no hard rate limits, while requests to the Harvest API are &lt;a href="https://developers.greenhouse.io/harvest.html#throttling" rel="noopener noreferrer"&gt;throttled within a 10-second window&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity:&lt;/strong&gt; Since the Job Board API is publicly accessible, you can use it with purely client-side applications. For the Harvest API, you will need to implement a backend to keep the API key secure and to filter out internal data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the other hand, the Job Board API doesn’t expose some data, which may be helpful for building custom integrations. Particularly lacking are external IDs of &lt;a href="https://developers.greenhouse.io/harvest.html#the-office-object" rel="noopener noreferrer"&gt;offices&lt;/a&gt; and &lt;a href="https://developers.greenhouse.io/harvest.html#the-department-object" rel="noopener noreferrer"&gt;departments&lt;/a&gt; (useful for mapping office or department descriptions to an external CMS), or &lt;a href="https://developers.greenhouse.io/harvest.html#the-job-post-object" rel="noopener noreferrer"&gt;the time when a job was first published&lt;/a&gt; (for displaying jobs which were recently added, not updated).&lt;/p&gt;

&lt;h2&gt;
  
  
  Candidate Ingestion API for sourcing partners
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developers.greenhouse.io/candidate-ingestion.html#introduction" rel="noopener noreferrer"&gt;Candidate Ingestion API&lt;/a&gt; is intended for sourcing partners, like external job portals and agencies. It provides limited access to jobs and candidates, as well as the ability to post new candidates.&lt;/p&gt;

&lt;p&gt;Similarly to the Harvest API, the Candidate Ingestion API requires HTTP Basic authentication with the API key as the username, an empty password, and the &lt;code&gt;On-Behalf-Of&lt;/code&gt; header. However, the &lt;code&gt;On-Behalf-Of&lt;/code&gt; must be set to the user’s e-mail. The permissions of the integration are limited to the user’s role. Typically, you will have a dedicated “service account” for the integration.&lt;/p&gt;

&lt;p&gt;In this JavaScript sample, we’re listing jobs using the Candidate Ingestion API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create Candidate Ingestion API key in Configure &amp;gt; Dev Center &amp;gt; API Credentials; in Partners, select Resource (dev)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GREENHOUSE_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`321a2ff8cdcdeadbeefd372a9c1a69e9-3`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Your email or for a dedicated service account&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ON_BEHALF_OF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demo@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// base64 encode credentials&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GREENHOUSE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.greenhouse.io/v1/partner/jobs`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;On-Behalf-Of&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ON_BEHALF_OF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Unexpected response from the server: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Candidate Ingestion API also provides &lt;a href="https://developers.greenhouse.io/candidate-ingestion.html#authentication" rel="noopener noreferrer"&gt;OAuth 2.0 authorization&lt;/a&gt;, with granular scopes for viewing jobs, candidates, and creating candidates. Consumer key and secret are provided by Greenhouse. The access token is bound to the user who authorized the application, and therefore the &lt;code&gt;On-Behalf-Of&lt;/code&gt; header is not needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time to build the integration
&lt;/h2&gt;

&lt;p&gt;Picking an API and getting the right API key is just the start. Now you’ll need to read the documentation, figure out the resources, properties, API calls… And once you build the integration, you also need to keep checking the API for changes and breakages.&lt;/p&gt;

&lt;p&gt;Maybe there’s a better way. We've already went through multiple ATSs and distilled their APIs into &lt;a href="https://superface.ai/domain/ats?utm_source=dev.to&amp;amp;utm_campaign=greenhouse-recruiting-api-overview-auth"&gt;ready-made ATS connectors&lt;/a&gt;, so you don’t need to read the docs and can focus on building your app instead.&lt;/p&gt;

&lt;p&gt;With Superface, you’ll get unified integration logic which shields your application from API changes, and provides enhanced monitoring. Our SDK provides direct, proxy-less integration with the external API, so it’s faster and respects the privacy of your candidates. And you’re not reliant on our ready-made connectors – you can modify the integration logic to suit your needs, and build your own connectors.&lt;/p&gt;

&lt;p&gt;If that sounds interesting, take a look at our &lt;a href="https://superface.ai/provider/greenhouse?utm_source=dev.to&amp;amp;utm_campaign=greenhouse-recruiting-api-overview-auth"&gt;Greenhouse ATS integrations&lt;/a&gt; – but we also provide &lt;a href="https://superface.ai/catalog?utm_source=dev.to&amp;amp;utm_campaign=greenhouse-recruiting-api-overview-auth"&gt;other integrations&lt;/a&gt;, like geolocation, sending emails, Slack, and more. In case you have any questions or troubles with Greenhouse (or any other) API, meet us on &lt;a href="https://sfc.is/discord" rel="noopener noreferrer"&gt;our Discord server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’re posting more tutorials for APIs regularly. To stay in touch, &lt;a href="https://sfc.is/newsletter" rel="noopener noreferrer"&gt;subscribe to our newsletter&lt;/a&gt; or follow us here on &lt;a href="https://dev.to/superface"&gt;DEV&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/superfaceai" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or &lt;a href="https://twitter.com/superfaceai" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>ats</category>
    </item>
    <item>
      <title>LinkedIn's New Posts API: The Good, The Bad, and The Ugly</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Tue, 11 Apr 2023 09:00:00 +0000</pubDate>
      <link>https://dev.to/jnv/linkedins-new-posts-api-the-good-the-bad-and-the-ugly-5e53</link>
      <guid>https://dev.to/jnv/linkedins-new-posts-api-the-good-the-bad-and-the-ugly-5e53</guid>
      <description>&lt;p&gt;Last summer, LinkedIn &lt;a href="https://www.linkedin.com/developers/news/featured-updates/versioning-content-launch"&gt;announced new API versioning&lt;/a&gt; and plans to migrate existing API endpoints to the new versioning scheme along with some other improvements. The first set of endpoints to be migrated by LinkedIn were &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/posts-api"&gt;Posts API&lt;/a&gt;, responsible for working with users' and business profiles' posts.&lt;/p&gt;

&lt;p&gt;There was also a deadline: by February 2023, existing API users must migrate to the new Posts API and the old endpoints will stop functioning. That gave integration partners around 8 months for migration, but apparently that was not sufficient. So on the last day of February, LinkedIn announced a deadline extension to June 30.&lt;/p&gt;

&lt;p&gt;Since I've migrated &lt;a href="https://superface.ai/social-media/publish-post?provider=linkedin"&gt;LinkedIn's integration through Superface&lt;/a&gt;, I wrote down a few notes about the overall experience and frustrations with LinkedIn's new API and this particular deprecation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The API is better
&lt;/h3&gt;

&lt;p&gt;I used to show the LinkedIn API as an example of poor API design. There were three endpoints for handling users' and organizations' posts (UGC Posts, Shares, and Posts API which was in beta for a long time), with seemingly overlapping features.&lt;br&gt;
With &lt;a href="https://mastodon.social/@jnv/107842987885512426"&gt;weird, ad-hoc syntax&lt;/a&gt; for &lt;a href="https://learn.microsoft.com/en-us/linkedin/shared/api-guide/concepts/projections"&gt;field projections&lt;/a&gt; and &lt;a href="https://github.com/superfaceai/station/blob/9d2a052f1224b6076af78ff29bdb92dbbdf72eae/grid/social-media/posts/maps/linkedin.suma#L64-L65"&gt;annoyingly long property names&lt;/a&gt;, it wasn't the most pleasant API to work with.&lt;/p&gt;

&lt;p&gt;I'll have to find another poorly designed API now because the new Posts API is definitely an overall improvement. Deeply nested structures with confusing properties and arbitrary nesting of arrays – it's all gone.&lt;/p&gt;

&lt;p&gt;To illustrate the difference, here is the same post as represented by the legacy &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api"&gt;ugcPosts API&lt;/a&gt; and the new versioned Posts API:&lt;/p&gt;
&lt;h4&gt;
  
  
  Post from ugcPosts API (legacy)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lifecycleState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLISHED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"specificContent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"com.linkedin.ugc.ShareContent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shareCommentary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"inferredLocale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en_US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Don't forget the image."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Image"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:digitalmediaAsset:D4E10AQE71V5w_-aalA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"thumbnails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"overlayMetadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"tapTargets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"stickers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"overlayTexts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"READY"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shareFeatures"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hashtags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shareMediaCategory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IMAGE"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"com.linkedin.ugc.MemberNetworkVisibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"actor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:person:bDTsVFtMTq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1679663763610&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:organization:2414183"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clientApplication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:developerApplication:208506072"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"versionTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:share:7045020441609936898"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"firstPublishedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1679663764088&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastModified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"actor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:csUser:7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1679663764133&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"externalDistributionChannels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"distributedViaFollowFeed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"feedDistribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MAIN_FEED"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentCertificationRecord"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;originCountryCode&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;nl&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;modifiedAt&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:1679663763588,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;spamRestriction&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;classifications&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[],&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;contentQualityClassifications&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[],&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;systemName&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MACHINE_SYNC&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;lowQuality&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:false,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;contentClassificationTrackingId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;F00EA9A4CF8AA4C780241D4CE87D5E87&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;contentRelevanceClassifications&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[],&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;spam&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:false},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;contentHash&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;extractedContentMd5Hash&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;7871A7EF3ADBC18955073E68D2203F27&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;lastModifiedAt&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:1679663763587}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Post from the Posts API (new, versioned)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isReshareDisabledByAuthor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1679663763610&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lifecycleState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLISHED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastModifiedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1679663764133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1679663764088&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:organization:2414183"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:share:7045020441609936898"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"feedDistribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MAIN_FEED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"thirdPartyDistributionChannels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"altText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:image:D4E10AQE71V5w_-aalA"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"commentary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Don't forget the image."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lifecycleStateInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isEditedByAuthor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clear versioning and deprecation policy
&lt;/h3&gt;

&lt;p&gt;The legacy APIs were all “version 2” as to be distinguished from even older “&lt;a href="https://web.archive.org/web/20180821140959/https://developer.linkedin.com/docs/rest-api"&gt;version 1 endpoints&lt;/a&gt;”. However, there was no further granularity in these versions. It seems to me that new features were introduced on new endpoints, which is probably why they ended up with three different endpoints for posts.&lt;/p&gt;

&lt;p&gt;For the versioned API “reboot” LinkedIn chose a &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/versioning"&gt;calendar-based versioning scheme&lt;/a&gt;. Each version is identified by year and month (e.g., &lt;code&gt;202303&lt;/code&gt;) and no guarantees about breaking changes between versions (as opposed to &lt;a href="https://semver.org/"&gt;semantic versioning&lt;/a&gt;). Per documentation, each API version is supported for one year and specifying the version is mandatory for each call. Therefore, one can quickly see how much their integration code is behind the current versions.&lt;/p&gt;

&lt;p&gt;I think calendar-based versioning is a right call for quickly evolving APIs, and it seems to be an ever more popular choice. GitHub &lt;a href="https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/"&gt;announced a similar versioning scheme&lt;/a&gt; in November. And combined with a stable support window (something what &lt;a href=""&gt;Facebook is doing with Graph API&lt;/a&gt;), it provides a predictability and stable pace for API consumers.&lt;/p&gt;

&lt;p&gt;However, it's not clear to me how exactly the deprecation will be handled, which I address below.&lt;/p&gt;
&lt;h3&gt;
  
  
  Communication to integration partners
&lt;/h3&gt;

&lt;p&gt;Maybe my expectations about LinkedIn API were too low, but I was pleasantly surprised how well the communication about deprecation was handled. There was a relatively long transition period of eight months (now extended), and all consecutive email communication and documentation pages contained a big reminder about the deprecation.&lt;/p&gt;

&lt;p&gt;Still, it's pretty usual the emails go amiss and no one checks the API documentation. The integration is working now, so why'd I need to check the docs? But LinkedIn did use the communication channels they have with partners to get the message across.&lt;/p&gt;
&lt;h3&gt;
  
  
  Responsive support
&lt;/h3&gt;

&lt;p&gt;Even more important was responsive support. While previously LinkedIn recommended asking questions using &lt;a href="https://stackoverflow.com/questions/tagged/linkedin-api"&gt;linkedin-api tag on Stack Overflow&lt;/a&gt; (which usually went unanswered), now they provide a support portal. I've submitted a ticket, and to my surprise, I've received a helpful answer from a support representative in less than 24 hours. While I'd prefer a public forum where I could search for existing solutions first, having a working support channel is an improvement in itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Bad Parts
&lt;/h2&gt;

&lt;p&gt;While LinkedIn got many things right, there are a few things which bug me.&lt;/p&gt;
&lt;h3&gt;
  
  
  The clean shut-off
&lt;/h3&gt;

&lt;p&gt;At this point, it's clear that the 8-month transition period was either too optimistic, or a planned “soft deadline” from the start. API deprecation is constant pain, since you need to wait on your integration partners to make the changes. If your partners are paying for your product, you want to avoid pulling the rug from them. But if your partners are big enterprises, you can't expect them to react quickly to your changes. But I think LinkedIn could have done a few things to hasten the migration.&lt;/p&gt;

&lt;p&gt;At this point, it's not clear what actually &lt;em&gt;happens&lt;/em&gt; when LinkedIn shuts off the deprecated endpoints. Will they return an HTTP status error &lt;code&gt;410 Gone&lt;/code&gt; with a JSON message explaining the situation? Will they return a proxy error as HTML page? Or will they redirect to a Rick Roll? (Probably not the last option.)&lt;/p&gt;

&lt;p&gt;One way to test the migration readiness is to schedule planned “brownouts”. For example, GitHub uses this strategy for API deprecation (see &lt;a href="https://github.blog/changelog/2021-05-04-brownout-notice-api-authentication-via-query-parameters-and-the-oauth-applications-api-for-12-hours/"&gt;authentication changes notice&lt;/a&gt; for an example). GitHub schedules multiple 12 to 48 hour outages over the months before the deprecation, to simulate the final removal of the API. This is a great way to check whether the migration is complete as typically there's &lt;em&gt;that one more call&lt;/em&gt; no one migrated yet.&lt;/p&gt;

&lt;p&gt;I'm uncertain if this is an acceptable strategy for LinkedIn, both from a technical and business standpoint. But it seems more sensible to me than just to pull the plug on the final day.&lt;/p&gt;
&lt;h3&gt;
  
  
  Removed features in favor of simplicity
&lt;/h3&gt;

&lt;p&gt;I mentioned that the new API removed &lt;a href="https://learn.microsoft.com/en-us/linkedin/shared/api-guide/concepts/projections"&gt;field projections&lt;/a&gt; with weird and poorly documented syntax. The downside is that there's no equivalent feature in the new API.&lt;/p&gt;

&lt;p&gt;My typical use case for projections was to grab images and videos in posts with a single API request. To achieve the same functionality now, I need to collect media IDs from posts and resolve them with separate API calls. I believe this leads to much simpler implementation on LinkedIn's side, and it can encourage clients to cache referenced media, but it still shifts some complexity on the client's side.&lt;/p&gt;
&lt;h3&gt;
  
  
  Not-so-opaque object IDs
&lt;/h3&gt;

&lt;p&gt;And speaking of media resolution, here's another catch.&lt;/p&gt;

&lt;p&gt;Take a look at these objects from the Posts API response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:ugcPost:7044823133844885504"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commentary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Post A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Some title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:video:C5605AQHzRSAmLcHkTA"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:share:7046413622230614016"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commentary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Post B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:image:D5622AQHy2GLswHBoSg"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, can you tell which post contains a video, and which contains an image?&lt;/p&gt;

&lt;p&gt;Obviously, you can tell by the &lt;code&gt;id&lt;/code&gt; property (&lt;code&gt;urn:li:video&lt;/code&gt; vs. &lt;code&gt;urn:li:image&lt;/code&gt;) but you &lt;em&gt;shouldn't have to&lt;/em&gt;. IDs should be opaque values.&lt;/p&gt;

&lt;p&gt;LinkedIn has separate endpoints to resolve &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/images-api?view=li-lms-2023-03#get-a-single-image"&gt;images&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/videos-api?view=li-lms-2023-03#get-a-video"&gt;videos&lt;/a&gt;, so you need to do string match the ID to figure out which endpoint to call.&lt;/p&gt;

&lt;p&gt;Here are a few approaches how this could be improved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a new endpoint for resolving any media, where I can pass both video and image IDs (e.g., &lt;code&gt;/rest/media?ids=List(urn:li:video:...,urn:li:image:...)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Add a new property identifying the type of media:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:share:7046413622230614016"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commentary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Post B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:image:D5622AQHy2GLswHBoSg"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Or provide me with a link to the resource (although it doesn't match the style of this API):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:share:7046413622230614016"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commentary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Post B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.linkedin.com/rest/images/urn:li:image:D5622AQHy2GLswHBoSg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:image:D5622AQHy2GLswHBoSg"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Ugly Parts
&lt;/h2&gt;

&lt;p&gt;Some API changes are painful and ugly, but they have their reasons and maybe they'll be resolved in time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scrape it yourself, will you?
&lt;/h3&gt;

&lt;p&gt;Most social media, like Facebook or Twitter, automatically generate a “preview card” from a link contained in a post. There are some slight differences when publishing with API, for example, Facebook &lt;a href="https://developers.facebook.com/docs/graph-api/reference/v16.0/page/feed#custom-image"&gt;accepts a custom title, description, and thumbnail&lt;/a&gt; for the preview card – as long as the link points to a domain with verified ownership. Twitter, on the other hand, doesn't allow any preview customization during publishing.&lt;/p&gt;

&lt;p&gt;LinkedIn used to generate a link preview automatically when a post was published through the legacy &lt;code&gt;ugcPosts&lt;/code&gt; API. In the Posts API, this functionality &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/posts-api?view=li-lms-2023-03&amp;amp;tabs=http#article-post-creation-sample-request"&gt;has been removed&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Posts API does not support URL scraping for article post creation as it introduces level of unpredictability in how a post is going to look when API partners create it. Instead, API partners need to set article fields such as thumbnail, title and description within the post when creating an article post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fundamentally, I agree with this approach. I've experienced first-hand customer complaints about articles with missing or incorrect thumbnails. Usually these were caused by a disparity between the preview generated by a 3rd-party application, and LinkedIn's preview scraper. Furthermore, LinkedIn's scraped previews were impossible to refresh, so sometimes I had to instruct customers to add dummy query string to the links they share just to get a correct preview.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;So putting the responsibility for generating a link preview fully on the API clients' side makes sense. Still, it adds an extra complexity to the publishing process.&lt;/p&gt;

&lt;p&gt;Sure, you could just willy-nilly scrape arbitrary pages you want to publish. But every so often that won't work. I've dealt with websites whose owners were paranoid about &lt;em&gt;any&lt;/em&gt; scraping, and blocked any requests coming from unknown bots. In the end, they allowlisted our link preview scraper, but it took some negotiation.&lt;/p&gt;

&lt;p&gt;LinkedIn could simplify this process, and help both developers and paranoid site owners, by providing a separate API endpoint for scraping URL previews. Similar to Facebook, which &lt;a href="https://developers.facebook.com/docs/graph-api/reference/v16.0/url"&gt;has this feature&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The migration guide is somewhat useless
&lt;/h3&gt;

&lt;p&gt;LinkedIn provides convenient &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/integrations/migrations"&gt;migration guides&lt;/a&gt; for individual APIs.&lt;br&gt;
Unfortunately, the &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/contentapi-migration-guide"&gt;Content APIs migration guide&lt;/a&gt; left me struggling with the new API. Sure, it describes how individual fields in schemas were renamed, simplified, or removed (although a direct JSON to JSON comparison would be probably more descriptive) and it briefly describes how the workflow changed. But it's still too brief.&lt;/p&gt;

&lt;p&gt;If you need me to change the workflow, show me step by step how it differs from the old one. Even better, show me some code. The guide also doesn't mention anything about the removal of field projections, but maybe the usage of this feature was far too marginal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not-so-clear deprecation strategy
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/versioning?view=li-lms-2023-03#keep-up-with-us"&gt;versioning guide mentions&lt;/a&gt; that LinkedIn expects their partners to “keep with them”:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;LinkedIn expects that our LinkedIn Marketing API Program API partners work to deliver the latest and most valuable experiences to our customers within a reasonable time of their availability. &lt;em&gt;As a result, we will sunset our API versions as early as one (1) year after release.&lt;/em&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since no versioned API reached its end-of-life yet, I have yet to see what “sunsetting an API version” means. I think it could be one of these options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The requests start immediately return an error on the first day of the 13th month. (But what error? What status code?)&lt;/li&gt;
&lt;li&gt;The requests will probably work for some time, but can break at any time – it's your risk to call an outdated API.&lt;/li&gt;
&lt;li&gt;We will automatically redirect your outdated calls to a newer API version, and it will work as long as we don't introduce any breaking changes to the request schema.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third option is something of what Facebook does. I occasionally run into code using 5+ years old API versions,&lt;sup id="fnref3"&gt;3&lt;/sup&gt; and it still works. I suspect LinkedIn could go with the first or second option. If this is the case, I hope they'll provide developers with an early warning that their integrations are about to break. In other words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide developers with API versions usage in app analytics.&lt;/li&gt;
&lt;li&gt;Describe in gory details what exactly happens after the API reaches the end-of-life.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So, that's probably far too many words about the new LinkedIn API. Despite my criticism, I think it's still an overall improvement, and I'm glad LinkedIn takes the developer experience seriously, unlike other social media (&lt;a href="https://dev.to/jnv/is-twitter-api-free-ive-built-a-website-to-find-out-3hn4"&gt;ahem&lt;/a&gt;).&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Unlike Facebook, which provides a &lt;a href="https://developers.facebook.com/tools/debug/"&gt;convenient tool&lt;/a&gt; for debugging and refreshing link previews. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Emphasis mine. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;In &lt;a href="https://github.com/jaredhanson/passport-facebook/blob/22aaab2a5c8b036e68287aa32ebe8f2bb68afb5c/lib/strategy.js#L50"&gt;passport-facebook&lt;/a&gt;, for example. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>api</category>
      <category>linkedin</category>
      <category>socialmedia</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Twitter API Changes: The missing FAQ for Free &amp; Basic access</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Fri, 07 Apr 2023 08:11:22 +0000</pubDate>
      <link>https://dev.to/superface/twitter-api-changes-the-missing-faq-for-free-basic-access-2lil</link>
      <guid>https://dev.to/superface/twitter-api-changes-the-missing-faq-for-free-basic-access-2lil</guid>
      <description>&lt;p&gt;Last week, the new Twitter API access tiers &lt;a href="https://twitter.com/TwitterDev/status/1641222782594990080" rel="noopener noreferrer"&gt;were finally announced&lt;/a&gt;. Unfortunately, some important details were left out from the announcement, leaving many developers confused and stressed out as the &lt;a href="https://twitter.com/TwitterDev/status/1641222786894135296" rel="noopener noreferrer"&gt;deprecation deadline&lt;/a&gt; is getting closer.&lt;/p&gt;

&lt;p&gt;At Superface, we maintain &lt;a href="https://superface.ai/catalog#social-media" rel="noopener noreferrer"&gt;social media integrations&lt;/a&gt;, including Twitter’s, and we’ve &lt;a href="https://dev.to/superface/how-to-use-twitter-oauth-20-and-passportjs-for-user-login-33fk"&gt;built an authorization library for Twitter API&lt;/a&gt;, so we’ve been closely observing the recent developments around Twitter API. (I’ve even made &lt;a href="https://istwitterapifree.com/" rel="noopener noreferrer"&gt;a site for that&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;In this article, I have collected observations and recommendations about Twitter’s new API. In summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If possible, don’t migrate to the new plans yet
&lt;/li&gt;
&lt;li&gt;
You can use Twitter Login for free both with OAuth 2.0 and OAuth 1.0a&lt;/li&gt;
&lt;li&gt;You need to migrate your app to API v2
&lt;/li&gt;
&lt;li&gt;You can post tweets with media
&lt;/li&gt;
&lt;li&gt;You can still embed tweets
&lt;/li&gt;
&lt;li&gt;If you need to do anything else than login users or post tweets, you'll have to pay a monthly fee (maybe even a large sum)&lt;/li&gt;
&lt;li&gt;Don’t rely on official support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a warning, though: Twitter is in constant flux, so any information in this article can become outdated at any time. I will do my best to keep it up to date – check the &lt;a href="https://superface.ai/blog/twitter-api-new-plans#changelog" rel="noopener noreferrer"&gt;changelog and the original article&lt;/a&gt; for updates. If you notice any false or outdated information, please let me know.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do we know from the announcement?
&lt;/h2&gt;

&lt;p&gt;The changes were announced on the &lt;a href="https://twitter.com/TwitterDev/status/1641222782594990080" rel="noopener noreferrer"&gt;Twitter Dev account&lt;/a&gt; and the &lt;a href="https://twittercommunity.com/t/announcing-new-access-tiers-for-the-twitter-api/188728" rel="noopener noreferrer"&gt;community forums&lt;/a&gt;. Here's what we can learn from these announcements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All existing API access tiers (Standard, Premium, Essential, and Elevated) are being replaced by the new Free and Basic tiers.&lt;/li&gt;
&lt;li&gt;Both tiers allow users to log in with Twitter, read the profile of an authorized user, and post tweets on behalf of users.&lt;/li&gt;
&lt;li&gt;Only the paid Basic tier provides read access to user profiles and tweets at a much lower rate than previous tiers (10,000 tweets per month, compared to 500,000 on Essential and 2 million on Elevated).&lt;/li&gt;
&lt;li&gt;The Basic tier costs $100 / month.&lt;/li&gt;
&lt;li&gt;The v1.1 API is being deprecated in favor of the v2 API (with an exception for media uploads, see below).&lt;/li&gt;
&lt;li&gt;The previous plans and legacy API will be deprecated by April 29th, 2023 &lt;em&gt;at the latest&lt;/em&gt; – so technically the changes can happen any time sooner.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://developer.twitter.com/en/docs/twitter-ads-api" rel="noopener noreferrer"&gt;Twitter Ads API&lt;/a&gt; is unaffected by these changes.&lt;/li&gt;
&lt;li&gt;There are no special access plans for researchers and academics at this time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do I need to pay so the users of my app can log in with Twitter?
&lt;/h2&gt;

&lt;p&gt;No, Twitter Login is available on the Free plan.&lt;/p&gt;

&lt;p&gt;You can also read the information about a logged-in user through the &lt;a href="https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me" rel="noopener noreferrer"&gt;&lt;code&gt;GET /2/users/me&lt;/code&gt;&lt;/a&gt; endpoint. It is rate limited to 25 requests per 24 hours &lt;em&gt;per user&lt;/em&gt;, so just make sure your integration code doesn’t read this endpoint too frequently (or fails gracefully when you hit the limit).&lt;/p&gt;

&lt;p&gt;During my testing, I also frequently encountered a “Something went wrong” error on Twitter’s authorization page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcn3i6beeuc665h1ryik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcn3i6beeuc665h1ryik.png" alt="Screenshot of error: Something went wrong. You weren't able to give access to the App. Go back and try logging in again." width="487" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few retries, the authorization flow was successful. If your users encounter a similar issue, instruct them to just retry logging in a few times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I need to use OAuth 2.0 for login?
&lt;/h2&gt;

&lt;p&gt;No, both &lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-1-0a" rel="noopener noreferrer"&gt;OAuth 1.0a&lt;/a&gt; and &lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-2-0" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt; (with app-only and user contexts) are supported on both access tiers.&lt;/p&gt;

&lt;p&gt;You must use OAuth 1.0a if you want to publish tweets with images or videos. On the other hand, newer API features, like &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/bookmarks/introduction" rel="noopener noreferrer"&gt;Bookmarks&lt;/a&gt; or &lt;a href="https://developer.twitter.com/en/docs/twitter-api/spaces/overview" rel="noopener noreferrer"&gt;Spaces&lt;/a&gt; are available only with OAuth 2.0. Check the &lt;a href="https://developer.twitter.com/en/docs/authentication/guides/v2-authentication-mapping" rel="noopener noreferrer"&gt;Twitter v2 Authentication Mapping&lt;/a&gt; to see what features are supported in respective authentication contexts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I need to migrate to the Twitter API v2?
&lt;/h2&gt;

&lt;p&gt;Yes. According to the announcement, both Standard v1.1 and Premium v1.1 endpoints will be deprecated. The Basic tier is &lt;a href="https://developer.twitter.com/en#:~:text=Rate%20limited%20access%20to%20suite%20of%20v2%20endpoints%C2%A0" rel="noopener noreferrer"&gt;described&lt;/a&gt; as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rate limited access to suite of v2 endpoints&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The only exceptions are media upload endpoints, which are not available in the v2 API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I post tweets with media (images, GIFs, videos)?
&lt;/h2&gt;

&lt;p&gt;Yes, that’s possible even on the Free plan, but you need to combine v2 API endpoints with v1.1 media upload endpoints. You must use OAuth 1.0a with read+write access, as media upload endpoints don’t support OAuth 2.0 access tokens.&lt;/p&gt;

&lt;p&gt;Follow these steps to post a tweet with media attachments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload media using the &lt;a href="https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/overview" rel="noopener noreferrer"&gt;Upload media endpoints&lt;/a&gt;: &lt;a href="https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/api-reference/post-media-upload" rel="noopener noreferrer"&gt;&lt;code&gt;POST media/upload&lt;/code&gt;&lt;/a&gt; for images or &lt;a href="https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/api-reference/post-media-upload-init" rel="noopener noreferrer"&gt;chunked upload endpoints&lt;/a&gt; for videos.&lt;/li&gt;
&lt;li&gt;You will receive &lt;code&gt;media_id&lt;/code&gt; for the uploaded objects.&lt;/li&gt;
&lt;li&gt;Post a tweet using the &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets" rel="noopener noreferrer"&gt;&lt;code&gt;POST /2/tweets&lt;/code&gt;&lt;/a&gt; endpoint and reference the uploaded media objects in a &lt;code&gt;media&lt;/code&gt; property like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tweet with media"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"media_ids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"1455952740635586573"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Can I only post tweets with Free access?
&lt;/h2&gt;

&lt;p&gt;Mostly, yes. It’s possible only to &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/introduction" rel="noopener noreferrer"&gt;manage a user’s tweets&lt;/a&gt; (i.e., create, delete), upload media, and &lt;a href="https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me" rel="noopener noreferrer"&gt;look up information about the authorized user&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you hit a paid endpoint, you will get a non-descriptive error message like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Forbidden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"about:blank"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Forbidden"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notably, you can’t read &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/timelines/api-reference/get-users-id-tweets" rel="noopener noreferrer"&gt;user’s tweets timeline&lt;/a&gt; or the &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/timelines/api-reference/get-users-id-mentions" rel="noopener noreferrer"&gt;mentions timeline&lt;/a&gt;. So if you are building an application that tracks users’ mentions (e.g., social media care or analytics, or a bot that replies to tweets), you will have to pay for Basic access.&lt;/p&gt;

&lt;h2&gt;
  
  
  I need to read more than 10,000 tweets per month, what should I do?
&lt;/h2&gt;

&lt;p&gt;The next access tier after the Basic is Enterprise, which, &lt;a href="https://mashable.com/article/twitter-elon-musk-paid-enterprise-api-access-pricing" rel="noopener noreferrer"&gt;according to the leaked sales documents&lt;/a&gt;, starts at $42,000 per month. You can apply through the &lt;a href="https://developer.twitter.com/en#:~:text=Subscribe%20now-,Enterprise,-For%20businesses%20and" rel="noopener noreferrer"&gt;Twitter Developer Portal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No doubt there are other ways to get the data for cheaper or for free, but that’s outside the scope of this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I embed tweets?
&lt;/h2&gt;

&lt;p&gt;Yes, &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites" rel="noopener noreferrer"&gt;Twitter for Websites&lt;/a&gt; features remain unaffected by these changes, including &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/embedded-tweets/overview" rel="noopener noreferrer"&gt;Embedded Tweets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While there are &lt;a href="https://www.theverge.com/2023/4/6/23673043/twitter-substack-embeds-bots-tools-api" rel="noopener noreferrer"&gt;reports of broken embeds&lt;/a&gt;, they are usually caused by suspended access to the Twitter API. For example, &lt;a href="https://twitter.com/SubstackInc/status/1644059805315747844" rel="noopener noreferrer"&gt;Substack reported issues with embeds&lt;/a&gt;, however, they use custom embeds unrelated to the official widgets. If you embed tweets on your own by fetching them from API, you will need to pay at least for the basic plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should I migrate to the new plans now?
&lt;/h2&gt;

&lt;p&gt;No. If you currently have Essential or Elevated access, I recommend stalling the migration to the Free and Basic plans at least until the limits enforcement stabilizes a bit. On the community forums, some users &lt;a href="https://twittercommunity.com/t/refund-basic-tier/188720" rel="noopener noreferrer"&gt;report losing access&lt;/a&gt; after purchasing the Basic access, probably because they were over the 10,000 tweets/month limit at the time of the purchase. Other users report &lt;a href="https://twittercommunity.com/t/free-tier-twitter-v2-return-too-many-requests-error-even-though-rate-not-exceed-3000-tweets-per-month-nor-100-tweets-per-day/189232" rel="noopener noreferrer"&gt;issues with rate limits&lt;/a&gt;. These bugs seem to be symptoms of rushed development. We can hope that they will be fixed before the April 29th deadline.&lt;/p&gt;

&lt;p&gt;Consider setting up a separate developer account with either Free or Basic access and test your application there first, before migrating your main account.&lt;/p&gt;

&lt;p&gt;If you're using API v1.1, you should migrate your application to API v2. The new API is accessible both on the Essential and Elevated plans. Check &lt;a href="https://developer.twitter.com/en/docs/twitter-api/migrate/overview" rel="noopener noreferrer"&gt;Twitter’s migration guides&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And of course, be prepared that &lt;a href="https://www.theverge.com/2023/4/6/23673043/twitter-substack-embeds-bots-tools-api" rel="noopener noreferrer"&gt;Twitter can suspend your application at any time&lt;/a&gt;, migrated or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where can I get help with Twitter API?
&lt;/h2&gt;

&lt;p&gt;If you run into issues with migration to the new access plans, don’t expect any help or refund from Twitter. As Ryan Barrett on the Twitter Developers forum points out, you can &lt;a href="https://twittercommunity.com/t/the-twitter-api-is-now-effectively-unmaintained/186011" rel="noopener noreferrer"&gt;treat the Twitter API as effectively unmaintained&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Still, the &lt;a href="https://twittercommunity.com/" rel="noopener noreferrer"&gt;Twitter Developers forum&lt;/a&gt; is probably the best place where you can get help from community volunteers and a good place to search for known issues.&lt;/p&gt;

&lt;p&gt;If you’re migrating a Node.js application to API v2, take a look at our &lt;a href="https://superface.ai/catalog#social-media" rel="noopener noreferrer"&gt;ready-made social media integrations&lt;/a&gt;, and our &lt;a href="https://superface.ai/blog/twitter-oauth2-passport" rel="noopener noreferrer"&gt;Twitter OAuth 2.0 strategy for Passport.js&lt;/a&gt;. We'll be happy to help you with Twitter API on &lt;a href="https://sfc.is/discord" rel="noopener noreferrer"&gt;Superface Discord&lt;/a&gt; or you can &lt;a href="https://superface.ai/support" rel="noopener noreferrer"&gt;reach directly to us&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your article is wrong or outdated / I still have questions
&lt;/h2&gt;

&lt;p&gt;Please – let me know! Leave a comment or reach out to me on &lt;a href="https://mastodon.social/@jnv" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; or &lt;a href="https://sfc.is/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m also sending out a monthly Superface Newsletter, so if you’d like to stay in touch and get more articles like this one with a mix of API and AI news, &lt;a href="https://sfc.is/newsletter" rel="noopener noreferrer"&gt;feel free to subscribe&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>twitter</category>
      <category>api</category>
      <category>discuss</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>Is Twitter API (still) Free? I've built a website to find out</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Wed, 08 Feb 2023 02:22:36 +0000</pubDate>
      <link>https://dev.to/jnv/is-twitter-api-free-ive-built-a-website-to-find-out-3hn4</link>
      <guid>https://dev.to/jnv/is-twitter-api-free-ive-built-a-website-to-find-out-3hn4</guid>
      <description>&lt;p&gt;Last week, Twitter announced the end of free access to its public API. The announcement came &lt;a href="https://twitter.com/TwitterDev/status/1621026986784337922" rel="noopener noreferrer"&gt;in a Tweet&lt;/a&gt;, a single week before the change, lacked any details about the pricing, and only &lt;a href="https://twitter.com/TwitterDev/status/1621027418680229888" rel="noopener noreferrer"&gt;vaguely promised more information&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today is February 8th, one day before the announced deadline. No further information was provided on the TwitterDev account, community forums, or developer newsletter. Only more vague promises of a free “write-only API for bots providing good content” in a &lt;a href="https://twitter.com/elonmusk/status/1622082025166442505" rel="noopener noreferrer"&gt;reply tweet&lt;/a&gt; by Mr. “Chief Twit” himself.&lt;/p&gt;

&lt;p&gt;To help fellow developers, I've made a website to provide a definitive answer to the question: &lt;a href="https://istwitterapifree.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Is Twitter API Free?&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A single-serving site
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0js78yr4puyd5wxurgxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0js78yr4puyd5wxurgxa.png" alt="Screenshot of the website: Is Twitter API free? Yes. Not for long. The deadline is tomorrow. But maybe you can use it for posting if Elon likes you" width="800" height="690"&gt;&lt;/a&gt;&lt;/p&gt;


istwitterapifree.com as of February 8th.





&lt;p&gt;There's a long tradition of &lt;a href="https://en.wikipedia.org/wiki/Single-serving_site" rel="noopener noreferrer"&gt;single-serving sites&lt;/a&gt;, like &lt;a href="http://tired.com/" rel="noopener noreferrer"&gt;Tired.com&lt;/a&gt; or &lt;a href="https://zombo.com/" rel="noopener noreferrer"&gt;Zombo.com&lt;/a&gt;. I wanted to build a single-serving site of my own, and this seemed like a good opportunity to make some fun out of this chaotic situation.&lt;/p&gt;

&lt;p&gt;It's also telling that Ryan King, Twitter employee number 33, &lt;a href="https://theryanking.com/post/is-twitter-down/" rel="noopener noreferrer"&gt;relaunched&lt;/a&gt; his &lt;a href="https://istwitterdown.com/" rel="noopener noreferrer"&gt;Is Twitter Down?&lt;/a&gt; site after more than a decade, reminding the old days of Fail Whale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the scenes
&lt;/h2&gt;

&lt;p&gt;I could've just made a static HTML site with “Yes”, and changed it to “No” once it'd be clear Twitter pulled the plug. That'd be the simplest thing to do, so I overengineered the whole thing, obviously.&lt;/p&gt;

&lt;p&gt;The website is periodically regenerated by GitHub Actions and deployed to GitHub Pages. During the build, a single Twitter API call is made for the existence of the &lt;a href="https://twitter.com/TwitterDev/status/1621026986784337922" rel="noopener noreferrer"&gt;announcement tweet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I don't know how exactly the Twitter API will communicate the paid access, but I assume that the API call will fail somehow. It's also possible that Twitter might remove the tweet if Mr. “Chief Twit” changes his mind. Therefore the success or failure of the call decides what is shown on the page.&lt;/p&gt;

&lt;p&gt;I challenged myself to avoid any extra packages, so the &lt;a href="https://github.com/jnv/istwitterapifree.com" rel="noopener noreferrer"&gt;site generator logic&lt;/a&gt; is written in vanilla JavaScript with a few Node.js built-in libraries. I'm using the built-in &lt;code&gt;fetch&lt;/code&gt; function, which is available without any flags since Node.js 18.&lt;/p&gt;

&lt;p&gt;I'm also curious about how many visitors my silly site gets. &lt;a href="https://www.goatcounter.com/" rel="noopener noreferrer"&gt;Goat Counter&lt;/a&gt; is a perfect web analytics tool for this kind of project, free and privacy-friendly. You can even view the traffic to the site &lt;a href="https://istwitterapifree.goatcounter.com/" rel="noopener noreferrer"&gt;on a public dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feed and the bots
&lt;/h2&gt;

&lt;p&gt;The site wouldn't be complete without a &lt;a href="https://twitter.com/IsTwApiFree" rel="noopener noreferrer"&gt;Twitter bot&lt;/a&gt; and a &lt;a href="https://masto.ai/@istwitterapifree" rel="noopener noreferrer"&gt;Mastodon bot&lt;/a&gt; (of course). I wanted to avoid implementing the posting logic, so I used the most unappreciated technology on the web: the good old RSS feed (well, technically an Atom feed).&lt;/p&gt;

&lt;p&gt;The site generator creates a &lt;a href="https://istwitterapifree.com/feed.xml" rel="noopener noreferrer"&gt;feed with a single entry&lt;/a&gt;. The entry is identified by the current date, so feed readers will display a new item at most once a day, regardless of how many times the site is regenerated.&lt;/p&gt;

&lt;p&gt;This feed is passed to automation. For Twitter, I've used IFTTT to post a new feed item to the Twitter account. I assume that a larger provider like IFTTT already pays for Twitter API, so it won't be affected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8yg38ipy14gssk2syf1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8yg38ipy14gssk2syf1.png" alt="Screenshot of IFTTT applet: If new feed item, then post a tweet." width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;


The whole magic behind the &lt;a href="https://twitter.com/IsTwApiFree" rel="noopener noreferrer"&gt;IsTwApiFree bot&lt;/a&gt;.





&lt;p&gt;For posting to Mastodon, I originally tried &lt;a href="https://mastofeed.org/" rel="noopener noreferrer"&gt;Mastofeed&lt;/a&gt;, but I haven't figured out a way to include the content of the feed entry in the post. I've turned to GitHub Actions, particularly &lt;a href="https://github.com/joschi/mastofeedbot" rel="noopener noreferrer"&gt;Mastofeedbot&lt;/a&gt;, which runs as a separate action after the site is updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson learned: Intl API
&lt;/h2&gt;

&lt;p&gt;The generated site displays how many days remain until the deadline. What helped me was JavaScript's built-in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl" rel="noopener noreferrer"&gt;Internationalization API&lt;/a&gt;, particularly &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat" rel="noopener noreferrer"&gt;Intl.RelativeTimeFormat&lt;/a&gt; to format the number of days to a string like “in 2 days” or “yesterday”. I'm calculating the number of days between &lt;code&gt;now&lt;/code&gt; and the assumed deadline, which is set to midnight on February 9 in Pacific time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1675929600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Feb 9 midnight PST&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Calculate millisecond difference between the dates&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diffMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Round the difference to number of days&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diffDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diffMs&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rtf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RelativeTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;localeMatcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;best fit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 3 → "in 3 days"&lt;/span&gt;
&lt;span class="c1"&gt;// 0 → "today"&lt;/span&gt;
&lt;span class="c1"&gt;// -1 → "yesterday" etc.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;daysRelative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rtf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diffDays&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;days&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;diffDays&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`The deadline is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysRelative&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`The deadline was &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysRelative&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the manipulation with dates is still painful, Intl APIs simplify at least formatting. I'm looking forward to the stable support for &lt;a href="https://tc39.es/proposal-temporal/docs/" rel="noopener noreferrer"&gt;Temporal API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;My hope for the &lt;a href="https://istwitterapifree.com/" rel="noopener noreferrer"&gt;Is Twitter API Free?&lt;/a&gt; site is that it becomes irrelevant in a few weeks. I might add some recommendations for developers who stumble upon the site, like resources for building ActivityPub bots or scraping Twitter's private API. No matter whether Twitter implements the API paywall in the end, or retracts the plan, it has already lost any remaining credibility as a platform for developers. It's time to move on.&lt;/p&gt;

&lt;p&gt;Meanwhile, keep checking &lt;a href="https://istwitterapifree.com/" rel="noopener noreferrer"&gt;the site&lt;/a&gt;, follow the &lt;a href="https://twitter.com/IsTwApiFree" rel="noopener noreferrer"&gt;Twitter bot&lt;/a&gt; or the &lt;a href="https://masto.ai/@istwitterapifree" rel="noopener noreferrer"&gt;Mastodon bot&lt;/a&gt;, and check the &lt;a href="https://github.com/jnv/istwitterapifree.com" rel="noopener noreferrer"&gt;site's source&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>announcement</category>
      <category>support</category>
      <category>offers</category>
    </item>
    <item>
      <title>Exploring Pocket API: Authorization</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Wed, 18 Jan 2023 22:59:16 +0000</pubDate>
      <link>https://dev.to/jnv/exploring-pocket-api-authorization-2ogb</link>
      <guid>https://dev.to/jnv/exploring-pocket-api-authorization-2ogb</guid>
      <description>&lt;p&gt;I've been using &lt;a href="https://getpocket.com/" rel="noopener noreferrer"&gt;Pocket&lt;/a&gt; for quite some time. Recently, I wanted to build something on top of their API. I've collected my notes and thoughts on Pocket API as a future reference for myself. Perhaps it will be useful to you.&lt;/p&gt;

&lt;p&gt;The first thing I needed to figure out was authorization. The good news is the authorization flow is &lt;a href="https://getpocket.com/developer/docs/authentication" rel="noopener noreferrer"&gt;well documented&lt;/a&gt;. The bad news is immediately in the first sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Pocket Authentication API uses &lt;em&gt;a variant of OAuth 2.0&lt;/em&gt; for authentication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Emphasis mine.)&lt;/p&gt;

&lt;p&gt;“Variant of OAuth 2.0” reeks of custom authorization schemes, which usually spells trouble. But while Pocket's authentication scheme is non-standard, it's actually closer to OAuth &lt;em&gt;1.0&lt;/em&gt; flow with “temporary credentials”. Minus all the request signing characteristic for OAuth 1.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pocket authorization vs. OAuth 2.0 Authorization Code Flow
&lt;/h2&gt;

&lt;p&gt;My simple understanding of a typical OAuth 2.0 Authorization Code Flow is this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy36bb180p5bhjkqflxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy36bb180p5bhjkqflxs.png" alt="OAuth 2.0 Authorization Code Flow redirects user back to the consumer app with authorization code. User passes the authorization code to the consumer app, which exchanges it for access token." width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The consumer application identifies itself by the client ID. The provider also keeps a list of allowed callback URLs, so it's not possible to steal the authorization code by redirecting the user to a malicious app.&lt;/p&gt;

&lt;p&gt;Pocket's authorization flow is a bit different:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fka0cy9y9rzvxfppib8kp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fka0cy9y9rzvxfppib8kp.png" alt="In Pocket authorization flow the consumer app first obtains a request token which user passes to the authorization to Pocket. After the user authorizaes the consumer app, the app exchanges the request token for an access token." width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The consumer app asks for a request token (“temporary credentials”) at the beginning of the flow, which it later exchanges for an access token. It's like getting a blank ticket and later validating it.&lt;/p&gt;

&lt;p&gt;In the Authorization Code Flow, the provider adds the authorization code to the callback URL, so there's no need to store any state during the authorization. In case of Pocket's flow, the request token needs to be stored somewhere, typically in a session or in a cookie.&lt;/p&gt;

&lt;p&gt;On the other hand, Pocket doesn't need to know a list of allowed URLs. Even if the user were redirected to a malicious client app, it wouldn't know the original request token and couldn't exchange it for an access token.&lt;/p&gt;

&lt;p&gt;The current version of Pocket API was &lt;a href="https://blog.getpocket.com/2012/11/introducing-the-new-pocket-api-for-developers-and-publishers/" rel="noopener noreferrer"&gt;introduced in 2012&lt;/a&gt; which is the same year when OAuth 2.0 was finished. So, I think the authorization scheme ended up somewhere in between OAuth 1.0 and 2.0: it's mostly OAuth 1.0 flow without requests signing, which was also removed in OAuth 2.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pocket authorizatiom in Node.js
&lt;/h2&gt;

&lt;p&gt;Since no API client tool like Postman or Hoppscotch can handle Pocket's authorizatiom scheme, I had to implement it on my own.&lt;/p&gt;

&lt;p&gt;Luckily, there are a few Node.js libraries handling the scheme, but most of them are over 5 years old. I've picked &lt;a href="https://github.com/mheap/pocket-auth" rel="noopener noreferrer"&gt;pocket-auth&lt;/a&gt; by Michael Heap, and got my access token with this code modified from the library's example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pocket-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;consumerKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRedirectUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Visit the following URL and click approve in the next 10 seconds:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You didn't click the link and approve the application in time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script will show a URL with the request token, and after 20 seconds it attempts to grab the access key – meanwhile, you need to authorize access to Pocket.&lt;/p&gt;

&lt;p&gt;Only later I've found that Michael also built a &lt;a href="https://github.com/mheap/pocket-auth-cli" rel="noopener noreferrer"&gt;CLI tool for pocket-auth&lt;/a&gt;, which is much more convenient. Just run the tool with consumer key as argument, and it will handle the whole flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx pocket-auth-cli &amp;lt;Pocket consumer key&amp;gt;
Opening web browser to authorize application
Press CTRL+C to cancel
&lt;span class="o"&gt;{&lt;/span&gt; access_token: &lt;span class="s1"&gt;'&amp;lt;redacted&amp;gt;'&lt;/span&gt;, username: &lt;span class="s1"&gt;'&amp;lt;redacted&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Onto retrieval
&lt;/h2&gt;

&lt;p&gt;This was a distracting but necessary step to get access to Pocket's API. Now it's time to retrieve some articles – but let's keep it for another time.&lt;/p&gt;

</description>
      <category>gratitude</category>
      <category>product</category>
    </item>
    <item>
      <title>Instagram Graph API Explained: How to log in users</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Tue, 03 Jan 2023 13:16:21 +0000</pubDate>
      <link>https://dev.to/superface/instagram-graph-api-explained-how-to-log-in-users-lp8</link>
      <guid>https://dev.to/superface/instagram-graph-api-explained-how-to-log-in-users-lp8</guid>
      <description>&lt;p&gt;There are two ways how to access the Instagram API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Through Instagram’s &lt;a href="https://developers.facebook.com/docs/instagram-basic-display-api" rel="noopener noreferrer"&gt;Basic Display API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Through Instagram Graph API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Instagram Basic Display API&lt;/strong&gt; is limited to read-only access to a user’s profile and &lt;strong&gt;isn’t intended for user authentication&lt;/strong&gt;. It’s useful, for example, to get up-to-date information about the user’s profile or display their recent posts. It also works with any Instagram account type: personal and professional.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;strong&gt;Instagram Graph API&lt;/strong&gt; allows you to publish posts, moderate comments, search hashtags, and access insights. However, the Graph API can be used only by “professional” accounts which have been &lt;a href="https://superface.ai/blog/instagram-setup" rel="noopener noreferrer"&gt;paired with a Facebook page&lt;/a&gt;. The authentication flow is handled through &lt;a href="https://developers.facebook.com/docs/facebook-login/" rel="noopener noreferrer"&gt;Facebook Login&lt;/a&gt;, and the user can pick which Facebook pages and Instagram accounts are available to the application.&lt;/p&gt;

&lt;p&gt;This article is part of a series about Instagram Graph API. Previously, I covered &lt;a href="https://superface.ai/blog/instagram-setup" rel="noopener noreferrer"&gt;test account and app setup&lt;/a&gt; and &lt;a href="https://superface.ai/blog/instagram-account-id" rel="noopener noreferrer"&gt;finding the correct Instagram account ID&lt;/a&gt;. In this part, I will show you how to implement the authentication flow for Instagram Graph API with Facebook Login. I will use Node.js, Express, and Passport with the &lt;code&gt;passport-facebook&lt;/code&gt; strategy, but the basic ideas are applicable to any language and framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Facebook Login Setup
&lt;/h2&gt;

&lt;p&gt;This tutorial assumes you have a Facebook application set up, and an Instagram business account paired with a Facebook page for testing. If not, &lt;a href="https://superface.ai/blog/instagram-setup" rel="noopener noreferrer"&gt;check my previous tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We will need to set up Facebook Login for our testing application.&lt;/p&gt;

&lt;p&gt;Find your application on the &lt;a href="https://developers.facebook.com/apps/" rel="noopener noreferrer"&gt;Facebook Developer Portal&lt;/a&gt;, and on the Dashboard, set up &lt;strong&gt;Facebook Login for Business&lt;/strong&gt;. If you didn’t select a “Business app type” when creating the application, you may see &lt;strong&gt;Facebook Login&lt;/strong&gt; instead, so choose that – both options will work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht3jzuc5e95a6f0fcwyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht3jzuc5e95a6f0fcwyk.png" alt="Application dashboard with Facebook Login for Business highlighted." width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under Facebook Login Settings, make sure to enable both &lt;strong&gt;Client OAuth login&lt;/strong&gt; and &lt;strong&gt;Web OAuth login&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr60q5w2c08j2z9p9aebk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr60q5w2c08j2z9p9aebk.png" alt="Screen for Facebook Login for Business settings, with toggles for “Client OAuth login” and “Web OAuth login” set to On" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This screen is also where you can add allowed redirect URIs for deployed application. We will run the application only locally, and Facebook allows &lt;code&gt;localhost&lt;/code&gt; URLs by default, so there’s no need to add any URL.&lt;/p&gt;

&lt;p&gt;Finally, visit application Settings &amp;gt; Basic, and copy the &lt;strong&gt;App ID&lt;/strong&gt; and &lt;strong&gt;App Secret&lt;/strong&gt;. We will need them for the Passport strategy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh6gb7hl6rbdhxyd6jbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh6gb7hl6rbdhxyd6jbu.png" alt="Application Basic Settings with fields for App ID and App secret" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Instagram Graph API authentication flow with Passport
&lt;/h2&gt;

&lt;p&gt;With Facebook Login now set up, we can build an authentication flow. I will use the following npm packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/express" rel="noopener noreferrer"&gt;express&lt;/a&gt; server&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/express-session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt; to handle user data persistence&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/passport" rel="noopener noreferrer"&gt;passport&lt;/a&gt; for authentication&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/passport-facebook" rel="noopener noreferrer"&gt;passport-facebook&lt;/a&gt; for handling Facebook Login flow&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt; to read secrets from &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@superfaceai/one-sdk" rel="noopener noreferrer"&gt;@superfaceai/one-sdk&lt;/a&gt; to get the list of accessible Instagram profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s set up the project and install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;instagram-facebook-login-passport
&lt;span class="nb"&gt;cd &lt;/span&gt;instagram-facebook-login-passport
npm &lt;span class="nb"&gt;install &lt;/span&gt;express express-session passport passport-facebook dotenv @superfaceai/one-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now create a &lt;code&gt;.env&lt;/code&gt; file, and paste in the App ID and App Secret values you obtained in Facebook app settings:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:3000
&lt;span class="nv"&gt;FACEBOOK_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"App ID from Settings"&lt;/span&gt;
&lt;span class="nv"&gt;FACEBOOK_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"App Secret from Settings"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I have also prepared the &lt;code&gt;BASE_URL&lt;/code&gt; variable, since we will be passing the callback URL during the authentication process. Keeping this value configurable makes it easier to take your app to production.&lt;/p&gt;

&lt;p&gt;And now, let's create a &lt;code&gt;server.js&lt;/code&gt; file with the following content:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express-session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;passport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passport&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Strategy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passport-facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SuperfaceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@superfaceai/one-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SuperfaceClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;1&amp;gt; Serialization and deserialization&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Use the Facebook strategy within Passport&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// &amp;lt;2&amp;gt; Strategy initialization&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FACEBOOK_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FACEBOOK_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callbackURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/facebook/callback`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;3&amp;gt; Verify callback&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;4&amp;gt; Session middleware initialization&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;saveUninitialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;5&amp;gt; Start authentication flow&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;6&amp;gt; Scopes&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pages_show_list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram_basic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram_content_publish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;7&amp;gt; Callback handler&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/facebook/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// &amp;lt;8&amp;gt; Obtaining profiles&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdkProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;social-media/publishing-profiles@1.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdkProfile&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GetProfilesForPublishing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;{},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`
        &amp;lt;h1&amp;gt;Authentication succeeded&amp;lt;/h1&amp;gt;
        &amp;lt;h2&amp;gt;User data&amp;lt;/h2&amp;gt;
        &amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/pre&amp;gt;
        &amp;lt;h2&amp;gt;Instagram profiles&amp;lt;/h2&amp;gt;
        &amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/pre&amp;gt;
        `&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run the server with &lt;code&gt;npm start&lt;/code&gt; and visit &lt;code&gt;http://localhost:3000/auth/facebook&lt;/code&gt;. You will be redirected to the Facebook login, where you select the Facebook pages and Instagram profiles the application can have access to. Make sure to select the correct Facebook page with the associated Instagram profile. Otherwise, you won’t be able to access that profile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fljzn3s05cw7ngub3bc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fljzn3s05cw7ngub3bc.png" alt="Screens from Facebook Login authorization flow. The first screen selects the Facebook pages the application has access to, the second selects the Instagram Accounts." width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After passing the flow, you should see the basic data about your profile and a list of Instagram profiles you have access to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchz8j832loa7950694dm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchz8j832loa7950694dm.png" alt="Screen from the example application with a header “Authentication succeeded”. Below the heading there are User data with user's display name, ID, and access token, and Instagram profiles with profile's ID, name, username, and avatar image URL." width="800" height="703"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Explaining the example code
&lt;/h2&gt;

&lt;p&gt;If you read my tutorial on &lt;a href="https://superface.ai/blog/twitter-oauth2-passport" rel="noopener noreferrer"&gt;Twitter OAuth 2.0 authentication&lt;/a&gt;, the example will seem very familiar. The most significant difference is the handling of access tokens and additional logic for obtaining Instagram profiles.&lt;/p&gt;

&lt;p&gt;I will explain the example in individual parts:&lt;/p&gt;
&lt;h3&gt;
  
  
  User serialization and deserialization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;1&amp;gt; Serialization and deserialization&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These functions serialize and deserialize the user to and from a session. In our example application, we keep all sessions in memory with no permanent storage, so we just pass the whole user object.&lt;/p&gt;

&lt;p&gt;Typically, you will persist the data in a database. In that case, you will store the user ID in the session, and upon deserialization, find the user in your database using the serialized ID, for example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOrCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The deserialized user object is then accessible through the &lt;strong&gt;&lt;code&gt;req.user&lt;/code&gt;&lt;/strong&gt; property in middleware functions. You can find more in the &lt;a href="https://www.passportjs.org/concepts/authentication/sessions/" rel="noopener noreferrer"&gt;Passport documentation on sessions&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy initialization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use the Facebook strategy within Passport&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// &amp;lt;2&amp;gt; Strategy initialization&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FACEBOOK_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FACEBOOK_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callbackURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/facebook/callback`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This code registers the &lt;code&gt;passport-facebook&lt;/code&gt; strategy with credentials obtained from the application settings. The callback URL must be absolute, and registered as a valid OAuth redirect URL (except for &lt;code&gt;localhost&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;
  
  
  Success callback
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;3&amp;gt; Verify callback&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The second argument to the strategy constructor is a &lt;a href="https://www.passportjs.org/concepts/authentication/strategies/#verify-function" rel="noopener noreferrer"&gt;verify function&lt;/a&gt;, which is called at the end of the successful authorization flow. The user has authorized your application, and you will receive their access token and basic information about their profile (in this case just the ID and full name of the user). Facebook API doesn’t provide &lt;code&gt;refreshToken&lt;/code&gt; (you can, instead, &lt;a href="https://developers.facebook.com/docs/facebook-login/guides/access-tokens/get-long-lived/" rel="noopener noreferrer"&gt;turn the access token into a long-lived token&lt;/a&gt;), therefore that value will be always empty.&lt;/p&gt;

&lt;p&gt;Here, you might want to update or create the user in your database and store the access token, so that you can access the API on behalf of the user. The &lt;code&gt;done&lt;/code&gt; callback should receive a user object, which is later available through &lt;code&gt;req.user&lt;/code&gt;. To keep things simple, I only passed the access token along with the profile data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Passport and Session middlewares initialization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;4&amp;gt; Session middleware initialization&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;saveUninitialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Passport needs to be initialized as middleware as well. And it requires a session middleware for storing state and user data. The most common session middleware is &lt;a href="https://github.com/expressjs/session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By default, express-session stores all data in memory, which is good for testing, but not intended for production: if your server gets restarted, all users will be logged out. There is a wide selection of &lt;a href="https://github.com/expressjs/session#compatible-session-stores" rel="noopener noreferrer"&gt;compatible session stores&lt;/a&gt; – pick one which fits with the rest of your stack.&lt;/p&gt;

&lt;p&gt;Before you put this code into production, &lt;strong&gt;make sure to change the value of &lt;code&gt;secret&lt;/code&gt;&lt;/strong&gt;. This value is used to sign the session cookie. Keeping it easily guessable increases the risk of session hijacking. Check the &lt;a href="https://github.com/expressjs/session#secret" rel="noopener noreferrer"&gt;express-session docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Start the authentication flow
&lt;/h3&gt;

&lt;p&gt;Now we are getting to the actual route handlers where the authentication happens.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;5&amp;gt; Start authentication flow&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;6&amp;gt; Scopes&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pages_show_list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram_basic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram_content_publish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;passport.authenticate&lt;/code&gt; creates a &lt;a href="https://www.passportjs.org/concepts/authentication/middleware/" rel="noopener noreferrer"&gt;middleware&lt;/a&gt; for the given strategy. It redirects the user to Facebook with URL parameters, so that Facebook knows what application is the user authorizing and where the user should be then redirected back. The &lt;code&gt;authenticate&lt;/code&gt; function accepts a second parameter with additional options, where the most important is &lt;code&gt;scopes&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Authorization scopes (permissions)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// &amp;lt;6&amp;gt; Scopes&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pages_show_list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram_basic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram_content_publish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://oauth.net/2/scope/" rel="noopener noreferrer"&gt;OAuth scopes&lt;/a&gt; define what the application is allowed to do on behalf of the user (Facebook calls them “permissions”). The user can then review and approve these permissions.&lt;/p&gt;

&lt;p&gt;In this case, we request the following permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.facebook.com/docs/permissions/reference/pages_show_list/" rel="noopener noreferrer"&gt;&lt;code&gt;pages_show_list&lt;/code&gt;&lt;/a&gt; to list Facebook pages the user manages and allowed for our app&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.facebook.com/docs/permissions/reference/instagram_basic" rel="noopener noreferrer"&gt;&lt;code&gt;instagram_basic&lt;/code&gt;&lt;/a&gt; to read basic information about an Instagram profile&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.facebook.com/docs/permissions/reference/instagram_content_publish" rel="noopener noreferrer"&gt;&lt;code&gt;instagram_content_publish&lt;/code&gt;&lt;/a&gt; to allow publishing content; this is not needed now, but may be useful in the next tutorial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find additional possible permissions in the &lt;a href="https://developers.facebook.com/docs/permissions/reference" rel="noopener noreferrer"&gt;Permissions Reference&lt;/a&gt;. Keep in mind that if you’d like your application to be publicly accessible, you will have to submit it for an &lt;a href="https://developers.facebook.com/docs/app-review" rel="noopener noreferrer"&gt;app review&lt;/a&gt;, and explain how permissions are used.&lt;/p&gt;
&lt;h3&gt;
  
  
  Callback handler
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;7&amp;gt; Callback handler&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/facebook/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;facebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the final step in the authentication flow. After the user authorized your application, they are redirected to the &lt;code&gt;/auth/twitter/callback&lt;/code&gt; route. The &lt;code&gt;passport.authenticate&lt;/code&gt; middleware is here again, but this time it checks the query parameters Facebook provided on redirect, and obtains access and refresh tokens.&lt;/p&gt;

&lt;p&gt;If the authentication succeeds, the next middleware function is called – typically you will display some success message to the user, or redirect them back to your application. Since the authentication passed, you can now find the user data in the &lt;code&gt;req.user&lt;/code&gt; property.&lt;/p&gt;
&lt;h3&gt;
  
  
  Obtaining Instagram profiles
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;8&amp;gt; Obtaining profiles&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdkProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;social-media/publishing-profiles@1.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdkProfile&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GetProfilesForPublishing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`
      &amp;lt;h1&amp;gt;Authentication succeeded&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;User data&amp;lt;/h2&amp;gt;
      &amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/pre&amp;gt;
      &amp;lt;h2&amp;gt;Instagram profiles&amp;lt;/h2&amp;gt;
      &amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/pre&amp;gt;
      `&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The route handler uses &lt;a href="https://github.com/superfaceai/one-sdk-js" rel="noopener noreferrer"&gt;Superface OneSDK&lt;/a&gt; to fetch basic data about Instagram profiles we have access to. I use &lt;a href="https://superface.ai/social-media/publishing-profiles?provider=instagram" rel="noopener noreferrer"&gt;GetProfilesForPublishing&lt;/a&gt; for that, which also works with Facebook, LinkedIn, Pinterest, and Twitter. The logic is the same as in the previous &lt;a href="https://superface.ai/blog/instagram-account-id#get-instagram-account-easier-way" rel="noopener noreferrer"&gt;Find the right account ID&lt;/a&gt; tutorial, except we are passing the access token stored in session from &lt;code&gt;req.user.accessToken&lt;/code&gt; (see the code for the Success callback).&lt;/p&gt;
&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;One issue with the example code is that both the user data and the access token are stored in memory. The user needs to go through the authentication flow every time the server is restarted. For a real-world use, you will need to persist the data using, for example, a configuration file or a database.&lt;/p&gt;

&lt;p&gt;In future tutorials, I will look at publishing of images and videos, searching hashtags, and retrieving posts and comments. If you don’t want to miss them, &lt;a href="https://sfc.is/newsletter" rel="noopener noreferrer"&gt;subscribe to our newsletter&lt;/a&gt; and follow our blog 👇&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__4472"&gt;
  &lt;a href="/superface" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4472%2Fb7c584a4-1f96-4529-b014-d6a1a0c32b33.png" alt="superface image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;Superface&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;
        Superface allows GPT builders to easily connect to any API, enabling them to create, retrieve, and manage data from external platforms.
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Did you run into any problems? I will be happy to help you on our &lt;a href="https://sfc.is/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>api</category>
      <category>javascript</category>
      <category>socialmedia</category>
      <category>instagram</category>
    </item>
    <item>
      <title>How to use Twitter OAuth 2.0 and Passport.js for user login</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Thu, 24 Nov 2022 13:26:43 +0000</pubDate>
      <link>https://dev.to/superface/how-to-use-twitter-oauth-20-and-passportjs-for-user-login-33fk</link>
      <guid>https://dev.to/superface/how-to-use-twitter-oauth-20-and-passportjs-for-user-login-33fk</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; Use the Node.js package &lt;a href="https://github.com/superfaceai/passport-twitter-oauth2" rel="noopener noreferrer"&gt;@superfaceai/passport-twitter-oauth2&lt;/a&gt; to handle user authentication with Twitter's v2 API and Passport.js.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/superfaceai" rel="noopener noreferrer"&gt;
        superfaceai
      &lt;/a&gt; / &lt;a href="https://github.com/superfaceai/passport-twitter-oauth2" rel="noopener noreferrer"&gt;
        passport-twitter-oauth2
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Twitter OAuth 2.0 Strategy for Passport for accessing Twitter API v2
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Twitter OAuth 2.0 Strategy for Passport&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;@superfaceai/passport-twitter-oauth2&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@superfaceai/passport-twitter-oauth2" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b394b19eafa8014f67da8d915a9031d14cc0d0501d9402f3d5add135f38b3365/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f4073757065726661636561692f70617373706f72742d747769747465722d6f6175746832" alt="npm"&gt;&lt;/a&gt;
&lt;a href="https://github.com/superfaceai/passport-twitter-oauth2LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2beec463b638eba6c2281b98fc183e325110d4ca559a194528d472bea2accaa4/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f4073757065726661636561692f70617373706f72742d747769747465722d6f6175746832" alt="license"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/5b0b311fdfd272241c303752e6a7007a1eb576d77629ca946bebd6908a8113d7/68747470733a2f2f696d672e736869656c64732e696f2f7374617469632f76313f6d6573736167653d5479706553637269707426266c6f676f436f6c6f723d66666666666626636f6c6f723d303037616363266c6162656c436f6c6f723d356335633563266c6162656c3d6275696c7425323077697468"&gt;&lt;img src="https://camo.githubusercontent.com/5b0b311fdfd272241c303752e6a7007a1eb576d77629ca946bebd6908a8113d7/68747470733a2f2f696d672e736869656c64732e696f2f7374617469632f76313f6d6573736167653d5479706553637269707426266c6f676f436f6c6f723d66666666666626636f6c6f723d303037616363266c6162656c436f6c6f723d356335633563266c6162656c3d6275696c7425323077697468" alt="TypeScript"&gt;&lt;/a&gt;
&lt;a href="https://github.com/orgs/superfaceai/discussions" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3ace67f1d928ceb0de3482d07149f4304e5b8c1c4a4ccb83bbfd8094b9392bf5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f64697363757373696f6e732f73757065726661636561692f2e6769746875623f6c6f676f3d676974687562266c6f676f436f6c6f723d666666" alt="GitHub Discussions"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://passportjs.org/" rel="nofollow noopener noreferrer"&gt;Passport&lt;/a&gt; strategy for authenticating with Twitter using &lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-2-0" rel="nofollow noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This module lets you authenticate using Twitter in your Node.js applications
By plugging into Passport, Twitter authentication can be integrated into any application or framework that supports
&lt;a href="http://www.senchalabs.org/connect/" rel="nofollow noopener noreferrer"&gt;Connect&lt;/a&gt;-style middleware, including
&lt;a href="http://expressjs.com/" rel="nofollow noopener noreferrer"&gt;Express&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twittercommunity.com/t/announcing-oauth-2-0-general-availability/163555" rel="nofollow noopener noreferrer"&gt;Twitter announced OAuth 2.0 general availability&lt;/a&gt; on December 14 2021 and encourages developers to use Twitter API v2.0 with OAuth 2.0 authentication.&lt;/p&gt;
&lt;p&gt;Twitter OAuth 2.0 implementation specifics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc7636" rel="nofollow noopener noreferrer"&gt;PKCE&lt;/a&gt; is required&lt;/li&gt;
&lt;li&gt;OAuth2 client credentials must be passed via &lt;code&gt;Authorization&lt;/code&gt; header for &lt;code&gt;confidential&lt;/code&gt; client types&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install @superfaceai/passport-twitter-oauth2&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;
Check our blog for a &lt;a href="https://superface.ai/blog/twitter-oauth2-passport?ref=github-passport-twitter-oauth2" rel="nofollow noopener noreferrer"&gt;complete tutorial with code explanation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Create an Application&lt;/h4&gt;

&lt;/div&gt;
&lt;p&gt;Before using &lt;code&gt;@superfaceai/passport-twitter-oauth2&lt;/code&gt;, you must register a project and an application with Twitter by following these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;go to &lt;a href="https://developer.twitter.com/" rel="nofollow noopener noreferrer"&gt;https://developer.twitter.com/&lt;/a&gt; and either sign up for a new account or sign in with existing one&lt;/li&gt;
&lt;li&gt;sign up…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/superfaceai/passport-twitter-oauth2" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Read on for the background, how to obtain Twitter credentials, example Express app, and code explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use OAuth 2.0 for Twitter API
&lt;/h2&gt;

&lt;p&gt;When you want to interact with Twitter's API or just provide a “social login” through Twitter, until last year you had to use &lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-1-0a" rel="noopener noreferrer"&gt;legacy OAuth 1.0a authentication&lt;/a&gt;. While it still has its uses, Twitter is transitioning to the new version of their API (v2) and &lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-2-0" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt; protocol, which is also used by other providers, like Facebook, Google, and LinkedIn.&lt;/p&gt;

&lt;p&gt;Besides being more widespread, Twitter's OAuth 2.0 has other advantages compared to OAuth 1.0a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It gives you full access to Twitter's v2 API (including the endpoints for &lt;a href="https://developer.twitter.com/en/docs/twitter-api/spaces/overview" rel="noopener noreferrer"&gt;Twitter Spaces&lt;/a&gt; and &lt;a href="https://developer.twitter.com/en/docs/twitter-api/edit-tweets" rel="noopener noreferrer"&gt;editing tweets&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;It lets you specify exactly what your application needs from the API through &lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code" rel="noopener noreferrer"&gt;scopes&lt;/a&gt;, so users have a clear idea what your app can and cannot do with their account; OAuth 1.0a has only three levels of access: “read”, “read + write”, and “read + write + access DMs”&lt;/li&gt;
&lt;li&gt;It is future-proof, as the development effort is focused on v2 API, for which OAuth 2.0 is the default authentication protocol.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we wanted to build &lt;a href="https://superface.ai/catalog#social-media" rel="noopener noreferrer"&gt;social media integrations&lt;/a&gt;, we couldn't find any up-to-date Node.js library to handle the authentication flow. So, we built one as a strategy for the popular &lt;a href="https://www.passportjs.org/" rel="noopener noreferrer"&gt;Passport.js&lt;/a&gt; authentication middleware.&lt;/p&gt;

&lt;p&gt;The strategy is available in &lt;code&gt;@superfaceai/passport-twitter-oauth2&lt;/code&gt; package. Recently we released a new minor version 1.2, which conducts a full rewrite to TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting OAuth 2.0 Client ID and Secret from Twitter
&lt;/h2&gt;

&lt;p&gt;To start using Twitter API, you need to &lt;a href="https://developer.twitter.com/" rel="noopener noreferrer"&gt;register for a developer account&lt;/a&gt; (including phone number verification). Once you register, you will be prompted to create the first application. You will immediately receive &lt;strong&gt;API Key&lt;/strong&gt; and &lt;strong&gt;API Key Secret&lt;/strong&gt; – but &lt;strong&gt;ignore them&lt;/strong&gt;, since these are &lt;strong&gt;only for OAuth 1.0&lt;/strong&gt;. Instead, go to your project's dashboard, there go to the App Settings of the only application you created.&lt;br&gt;
In application settings, find &lt;strong&gt;User authentication settings&lt;/strong&gt; and click Set up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ma1vc8a22zn8omvl8nm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ma1vc8a22zn8omvl8nm.png" alt="Twitter developer portal with application settings. In the User authentication settings section, there is a text 'User authentication not set up' and a 'Set up' button." width="800" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;User authentication settings&lt;/em&gt; make sure to select &lt;strong&gt;Type of App&lt;/strong&gt; as &lt;strong&gt;Web App, Automated App or Bot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;App info&lt;/strong&gt; enter the following URL under &lt;strong&gt;Callback URI / Redirect URL&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000/auth/twitter/callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a URL where the user can be redirected after approving the application. The callback will be handled by the example server we will build in the next section.&lt;/p&gt;

&lt;p&gt;Fill in also the &lt;strong&gt;Website URL&lt;/strong&gt;, since it is a required value. Feel free to ignore other fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85qhzsfc1umbq83ppuid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85qhzsfc1umbq83ppuid.png" alt="A page with User authentication set up opened. 'Type of app' is either 'Native app' or 'Web app, automated app, or bot' with the second option selected. In 'App info' the 'Callback URI / Redirect URL' is set to the localhost address." width="800" height="816"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you save the form, you will get &lt;strong&gt;OAuth 2.0 Client ID and Client Secret&lt;/strong&gt;. Make sure to &lt;strong&gt;write these values down&lt;/strong&gt;, since you can't reveal the secret later, only regenerate it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxv8h5domzm64qtfxzlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxv8h5domzm64qtfxzlf.png" alt="Prompt with OAuth 2.0 Client ID and Client Secret and a disclaimer that " width="634" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But in case you forget to save the secret, you can always regenerate it under Keys and Tokens section of your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1iq785grur5yny5vibck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1iq785grur5yny5vibck.png" alt="Detail of Keys and Tokens section in application settings, which includes OAuth 2.0 Clients and Secrets section. The Client Secret section allows displaying a hint or regenerate the secret." width="800" height="676"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Authenticating with Passport.js and Twitter in Express.js
&lt;/h2&gt;

&lt;p&gt;Let's create a simple Express server with Passport.js to show the authentication flow in action. Passport requires some session handling mechanism, usually through &lt;a href="https://www.npmjs.com/package/express-session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt;. I will also use the &lt;a href="https://www.npmjs.com/package/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt; package to load the credentials from &lt;code&gt;.env&lt;/code&gt; file to &lt;code&gt;process.environment&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;First, let's create a project and install dependencies:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;twitter-oauth2-passport-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;twitter-oauth2-passport-demo
npm &lt;span class="nb"&gt;install &lt;/span&gt;express express-session passport @superfaceai/passport-twitter-oauth2 dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now put your Client ID and Client Secret into &lt;code&gt;.env&lt;/code&gt; file in the root of your project. Use the following template and make sure to paste in the correct values from the developer portal:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BASE_URL=http://localhost:3000
TWITTER_CLIENT_ID="OAuth 2.0 Client ID from Twitter Developer portal"
TWITTER_CLIENT_SECRET="OAuth 2.0 Client Secret from Twitter Developer portal"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I have also prepared the &lt;code&gt;BASE_URL&lt;/code&gt; variable, since we will be passing the callback URL during the authentication, and keeping this value configurable makes it easier to take your app to production.&lt;br&gt;
And now, let's create &lt;code&gt;server.js&lt;/code&gt; file, and put in the following contents:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;passport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passport&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Strategy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@superfaceai/passport-twitter-oauth2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express-session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;1&amp;gt; Serialization and deserialization&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Use the Twitter OAuth2 strategy within Passport&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// &amp;lt;2&amp;gt; Strategy initialization&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confidential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callbackURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/twitter/callback`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;3&amp;gt; Verify callback&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;4&amp;gt; Passport and session middleware initialization&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;saveUninitialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;5&amp;gt; Start authentication flow&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;6&amp;gt; Scopes&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tweet.read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users.read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline.access&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// &amp;lt;7&amp;gt; Callback handler&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/twitter/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Authentication succeeded&amp;lt;/h1&amp;gt; User data: &amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/pre&amp;gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now run your server with &lt;code&gt;npm start&lt;/code&gt; and visit &lt;code&gt;http://localhost:3000/auth/twitter&lt;/code&gt;. You will be redirected to Twitter authorization page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1fywglcnqlg95idaq06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1fywglcnqlg95idaq06.png" alt="Twitter prompt: " width="457" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And once you authorize the app, you should see the user data from your profile and, in addition, &lt;code&gt;accessToken&lt;/code&gt; and &lt;code&gt;refreshToken&lt;/code&gt; will be logged into console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19f8ko3zwza210pp58jm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19f8ko3zwza210pp58jm.png" alt="Page from the application with the title " width="624" height="523"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Breaking down the example
&lt;/h2&gt;

&lt;p&gt;Let's break down the example code above a bit.&lt;/p&gt;
&lt;h3&gt;
  
  
  User serialization and deserialization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;1&amp;gt; Serialization and deserialization&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These functions serialize and deserialize user to and from session. In our example application, we keep all sessions in the memory with no permanent storage, so we just pass the whole user object.&lt;/p&gt;

&lt;p&gt;Typically, you will persist data in a database. In that case, you will store the user ID in the session, and upon deserialization find the user in your database using the serialized ID, for example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserializeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOrCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The deserialized user object is then accessible through &lt;code&gt;req.user&lt;/code&gt; property in middleware functions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy initialization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use the Twitter OAuth2 strategy within Passport&lt;/span&gt;
&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// &amp;lt;2&amp;gt; Strategy initialization&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confidential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callbackURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/twitter/callback`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To use an authentication strategy, it must be registered with Passport through &lt;code&gt;passport.use&lt;/code&gt;. Here, the Twitter OAuth 2.0 strategy is initialized with credentials from Twitter developer portal. The callback URL must be absolute and registered with Twitter – it's where the user is redirected after authorizing the application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clientType&lt;/code&gt; can be either &lt;code&gt;confidential&lt;/code&gt; or &lt;code&gt;public&lt;/code&gt;, but in case of server-side applications you will usually use &lt;code&gt;confidential&lt;/code&gt;. As &lt;a href="https://oauth.net/2/client-types/" rel="noopener noreferrer"&gt;explained in OAuth specification&lt;/a&gt;, &lt;code&gt;confidential&lt;/code&gt; clients can keep the secret safe, while &lt;code&gt;public&lt;/code&gt; clients, like mobile applications, can't.&lt;/p&gt;
&lt;h3&gt;
  
  
  Success callback
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;lt;3&amp;gt; Verify callback&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The second argument to the strategy constructor is a &lt;a href="https://www.passportjs.org/concepts/authentication/strategies/#verify-function" rel="noopener noreferrer"&gt;verify function&lt;/a&gt;. In case of OAuth-based strategies, it is called at the end of &lt;em&gt;successful&lt;/em&gt; authorization flow. The user has authorized your application, and you will receive their access token and (optionally) refresh token and user's profile (username, display name, profile image etc.).&lt;/p&gt;

&lt;p&gt;Now it's your turn: typically you will want to update or create the user in your database and store the tokens, so you can call the API with them. The &lt;code&gt;done&lt;/code&gt; callback should receive a user object, which is passed in &lt;code&gt;req.user&lt;/code&gt; property.&lt;/p&gt;
&lt;h3&gt;
  
  
  Passport and Session middlewares initialization
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;4&amp;gt; Passport and session middleware initialization&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;saveUninitialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Passport needs to be initialized as middleware as well. And it requires a session middleware for storing state and user data. The most common session middleware is &lt;a href="https://github.com/expressjs/session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By default, express-session stores all data in memory, which is good for testing, but not intended for production: if your server gets restarted, all users will be logged out. There is a wide selection of &lt;a href="https://github.com/expressjs/session#compatible-session-stores" rel="noopener noreferrer"&gt;compatible session stores&lt;/a&gt; – pick one which fits with the rest of your stack.&lt;/p&gt;
&lt;h3&gt;
  
  
  Start the authentication flow
&lt;/h3&gt;

&lt;p&gt;Now we get to the juicy part, routes where the authentication happens. The first route is &lt;code&gt;/auth/twitter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;5&amp;gt; Start authentication flow&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;passport.authenticate&lt;/code&gt; creates a &lt;a href="https://www.passportjs.org/concepts/authentication/middleware/" rel="noopener noreferrer"&gt;middleware&lt;/a&gt; for the given strategy. It redirects the user to Twitter with URL parameters, so Twitter knows what application the user is authorizing and where the user should be then redirected back. &lt;code&gt;authenticate&lt;/code&gt; function accepts a second parameter with additional options, where the most important is &lt;code&gt;scopes&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Authorization scopes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// &amp;lt;6&amp;gt; Scopes&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tweet.read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users.read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline.access&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://oauth.net/2/scope/" rel="noopener noreferrer"&gt;OAuth scopes&lt;/a&gt; define what the application is allowed to do on behalf of the user. The user can then review and approve these permissions.&lt;/p&gt;

&lt;p&gt;In this case, the following scopes are requested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tweet.read&lt;/code&gt; – allows reading of user's and others' tweets (including tweets from private accounts the user follows)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;users.read&lt;/code&gt; – allows reading information about users profiles&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;offline.access&lt;/code&gt; – allows access even when the user isn't signed in; in practice, the application receives the refresh token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both &lt;code&gt;tweet.read&lt;/code&gt; and &lt;code&gt;users.read&lt;/code&gt; are necessary for accessing information about the signed-in user. &lt;code&gt;offline.access&lt;/code&gt; is useful when you want to do something when the user isn't directly interacting with your application, for example if you post scheduled tweets, build a bot, or monitor mentions on Twitter.&lt;/p&gt;

&lt;p&gt;For a detailed overview of what scopes are used in Twitter's API, check &lt;a href="https://developer.twitter.com/en/docs/authentication/guides/v2-authentication-mapping" rel="noopener noreferrer"&gt;Twitter's authentication mapping&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Callback handler
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &amp;lt;7&amp;gt; Callback handler&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/twitter/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Authentication succeeded&amp;lt;/h1&amp;gt; User data: &amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/pre&amp;gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the final step in the authentication flow. After the user authorized your application, they are redirected to &lt;code&gt;/auth/twitter/callback&lt;/code&gt; route. The &lt;code&gt;passport.authenticate&lt;/code&gt; middleware is here again, but this time it checks query parameters Twitter provided on redirect, checks if everything is correct, and obtains access and refresh tokens.&lt;/p&gt;

&lt;p&gt;If the authentication succeeds, the next middleware function is called – typically you will display some success message to the user or redirect them back to your application. Since the authentication passed, you can now find the user data in &lt;code&gt;req.user&lt;/code&gt; property.&lt;/p&gt;
&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Passport.js isn't limited to just Express, you can use our strategy with other frameworks with Passport compatibility like &lt;a href="https://docs.nestjs.com/security/authentication" rel="noopener noreferrer"&gt;NestJS&lt;/a&gt;, &lt;a href="https://github.com/fastify/fastify-passport" rel="noopener noreferrer"&gt;Fastify&lt;/a&gt;, or &lt;a href="https://github.com/rkusa/koa-passport" rel="noopener noreferrer"&gt;Koa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the obtained access token, you can start integrating Twitter's API. Check out our &lt;a href="https://github.com/superfaceai/twitter-demo" rel="noopener noreferrer"&gt;twitter-demo&lt;/a&gt; repository with CLI scripts showing how to list followers, lookup posts by hashtag, or publish a tweet.&lt;/p&gt;

&lt;p&gt;We also have a bit more complex &lt;a href="https://github.com/superfaceai/social-media-demo" rel="noopener noreferrer"&gt;social-media-demo&lt;/a&gt; which demonstrates authentication and integrations with Facebook, Instagram, LinkedIn, and Twitter.&lt;/p&gt;

&lt;p&gt;I will be diving into more hands-on examples with Twitter, Instagram, and other social media in the future posts. Subscribe to our blog or &lt;a href="https://share-eu1.hsforms.com/17JDgYID1Qk2qBzYmZAW0WQfn34z" rel="noopener noreferrer"&gt;sign up for our monthly newsletter&lt;/a&gt;, so you don't miss it.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__4472"&gt;
  &lt;a href="/superface" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4472%2Fb7c584a4-1f96-4529-b014-d6a1a0c32b33.png" alt="superface image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;Superface&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;
        Superface allows GPT builders to easily connect to any API, enabling them to create, retrieve, and manage data from external platforms.
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/superfaceai/passport-twitter-oauth2" rel="noopener noreferrer"&gt;Try our strategy&lt;/a&gt; and let me know what you are building with it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://twittercommunity.com/t/announcing-oauth-2-0-general-availability/163555" rel="noopener noreferrer"&gt;Announcement of OAuth 2.0 General Availability&lt;/a&gt; by Twitter&lt;/li&gt;
&lt;li&gt;Twitter's guide &lt;a href="https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api" rel="noopener noreferrer"&gt;Getting Access to the Twitter API&lt;/a&gt; is useful for the general overview of required steps and use of various credentials.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.twitter.com/en/docs/authentication/guides/v2-authentication-mapping" rel="noopener noreferrer"&gt;Twitter API v2 authentication mapping&lt;/a&gt; for overview of what scopes are needed where.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code" rel="noopener noreferrer"&gt;OAuth 2.0 Authorization Code Flow with PKCE&lt;/a&gt; explains details of Twitter's OAuth 2.0 implementation, like lifetime of access and refresh tokens, glossary, and overview of all scopes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>How do you glue APIs together?</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Fri, 11 Nov 2022 13:56:44 +0000</pubDate>
      <link>https://dev.to/superface/how-do-you-glue-apis-together-3jep</link>
      <guid>https://dev.to/superface/how-do-you-glue-apis-together-3jep</guid>
      <description>&lt;p&gt;Say you want to connect two or more APIs, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whenever someone submits a form on our website, add their contact to HubSpot (or other CRM) and email them.&lt;/li&gt;
&lt;li&gt;When someone gives a star to my repository on GitHub, send me a Telegram message.&lt;/li&gt;
&lt;li&gt;Post a tweet when there's a new post on our blog.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How do you connect these different API integrations together?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do you use some low-code platform like Zapier, &lt;a href="https://n8n.io/"&gt;n8n&lt;/a&gt;, or &lt;a href="https://nodered.org/"&gt;Node-RED&lt;/a&gt;? Or do you prefer to “glue” the code manually? Or do you have a magical framework which does the hard work for you?&lt;/p&gt;

&lt;p&gt;And importantly, how much you like/hate your current approach, and how do you think it &lt;em&gt;should&lt;/em&gt; work?&lt;/p&gt;

&lt;p&gt;I am curious to know!&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Instagram API: Find the right account ID</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Mon, 26 Sep 2022 07:17:02 +0000</pubDate>
      <link>https://dev.to/superface/instagram-api-find-the-right-account-id-4k3j</link>
      <guid>https://dev.to/superface/instagram-api-find-the-right-account-id-4k3j</guid>
      <description>&lt;p&gt;To manage your Instagram account through an API, you need two things: a user access token and a business account ID. Getting an access token is easy, but figuring out the account ID takes a few steps and sometimes causes a confusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; This tutorial assumes you have an Instagram business account connected with a Facebook page and Facebook application with Instagram Graph API enabled. Check my previous article on &lt;a href="https://dev.to/superface/getting-started-with-instagram-api-the-setup-2mhi"&gt;how to set up a test account for Instagram API&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/superface" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4472%2Fb7c584a4-1f96-4529-b014-d6a1a0c32b33.png" alt="Superface" width="800" height="888"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1363%2Fc509f283-5020-499d-b2d4-ea81309b8f43.jpg" alt="" width="800" height="800"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/superface/getting-started-with-instagram-api-the-setup-2mhi" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Get started with Instagram API: The Setup&lt;/h2&gt;
      &lt;h3&gt;Jan Vlnas for Superface ・ Sep 23 '22&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#instagram&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#api&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#facebook&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;If you are currently troubleshooting requests to Instagram's API and can't wrap your head around their IDs, skip right away to common ID confusions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get an access token
&lt;/h2&gt;

&lt;p&gt;For the following steps, I will use &lt;a href="https://developers.facebook.com/tools/explorer/" rel="noopener noreferrer"&gt;Graph API Explorer&lt;/a&gt;. This is a useful tool for trying out Facebook's APIs and also to obtain access tokens for authorized API access.&lt;/p&gt;

&lt;p&gt;In the right sidebar, select the previously created application under “Meta App”. Make sure “User Token” is selected and add the following permissions: &lt;code&gt;instagram_basic&lt;/code&gt;, &lt;code&gt;pages_show_list&lt;/code&gt;, and &lt;code&gt;instagram_content_publish&lt;/code&gt; (this will come handy in later tutorials).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftc47xgsf18x2fgd1rrbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftc47xgsf18x2fgd1rrbf.png" alt="Detail of Graph API Explorer sidebar with selected Meta App, User Token, and required permissions" width="578" height="946"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, click “Generate Access Token”. Facebook will display a pop-up with prompts to authorize access to your Instagram and Facebook pages. Make sure to select &lt;strong&gt;both&lt;/strong&gt; your testing Instagram account and the associated Facebook page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9dn05ojpo404uv3y8za.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9dn05ojpo404uv3y8za.png" alt="Facebook authorization dialog with Instagram account selected" width="800" height="1053"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Find your Instagram account ID
&lt;/h2&gt;

&lt;p&gt;If you authorized the application correctly, you should be able to list Facebook pages the application has access to.&lt;/p&gt;

&lt;p&gt;Enter the following path to the Graph API Explorer: &lt;code&gt;me/accounts&lt;/code&gt;. After submitting, you should see Facebook pages you gave your application access to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjlvvczr6zhnk39ltxee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjlvvczr6zhnk39ltxee.png" alt="Graph API Explorer with path set to me/accounts and authorized page in response, page's ID is highlighted." width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, copy the value &lt;code&gt;id&lt;/code&gt; of your page and enter the following path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;page ID&amp;gt;?fields=instagram_business_account
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the response, you will see both the Facebook's page ID and the ID of your Instagram account. Copy the value of &lt;code&gt;id&lt;/code&gt; under &lt;code&gt;instagram_business_account&lt;/code&gt; – this ID is necessary for further interactions with your Instagram account via API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffu1oq06i2928yxgn2dmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffu1oq06i2928yxgn2dmu.png" alt="Graph API Explorer with path set to Facebook page ID and fields set to instagram_business_account. In response, there is a nested field “id” under instagram_business_account object." width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Get account details
&lt;/h2&gt;

&lt;p&gt;With this ID, we can retrieve basic information about our Instagram account, like its username, name, and profile picture. Try querying the following path with your Instagram business account ID:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;business account ID&amp;gt;?fields=id,name,username,profile_picture_url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw36273nxawz243zpzyp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw36273nxawz243zpzyp8.png" alt="Graph API Explorer with Instagram account details" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  All in single request
&lt;/h3&gt;

&lt;p&gt;There is also an undocumented way to retrieve Instagram account details in a single request from &lt;code&gt;me/accounts&lt;/code&gt;. This way, we can skip intermediary requests and retrieve authorized Instagram accounts directly:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;me/accounts?fields=instagram_business_account{id,name,username,profile_picture_url}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd337hor92s0y2glxt0hm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd337hor92s0y2glxt0hm.png" alt="Graph API Explorer with Instagram account details retrieved from me/accounts edge" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Facebook's Graph API, it is possible to traverse some edges within a single request – this is what the curly braces in the &lt;code&gt;fields&lt;/code&gt; query parameter are for. In other words, we are telling the API, “give me these fields for &lt;code&gt;instagram_business_account&lt;/code&gt; edge under &lt;code&gt;me/accounts&lt;/code&gt; edge”.&lt;/p&gt;
&lt;h2&gt;
  
  
  Common ID confusions
&lt;/h2&gt;

&lt;p&gt;A common source of mistakes when dealing with Graph API is use of an incorrect type ID. If the API doesn't behave like you expect, check if you have the right ID. In case of Instagram Graph API, we are dealing with the following IDs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Facebook Page ID – retrieved from &lt;code&gt;me/accounts&lt;/code&gt; endpoint identifies &lt;a href="https://developers.facebook.com/docs/instagram-api/reference/page" rel="noopener noreferrer"&gt;Page node&lt;/a&gt; and acts as an entry point to get an Instagram account associated with the Page.&lt;/li&gt;
&lt;li&gt;Instagram (Business) Account ID – retrieved from Page node under &lt;code&gt;instagram_business_account&lt;/code&gt; edge. Documentation refers to it as &lt;a href="https://developers.facebook.com/docs/instagram-api/reference/ig-user" rel="noopener noreferrer"&gt;IG User node&lt;/a&gt;. It must be accessed with &lt;em&gt;a user&lt;/em&gt; access token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ig_id&lt;/code&gt; or Instagram User ID – this is an ID of the Instagram account from the legacy, deprecated API. It is intended for migration of applications using the pre-graph API, but it's not used anywhere else. It's also represented as a number, while Graph API IDs are strings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Get Instagram account easier way
&lt;/h2&gt;

&lt;p&gt;Picking a correct Instagram account is a pretty basic integration task, so we've built an easier way to do that. If you use Node.js, before you grab &lt;code&gt;fetch&lt;/code&gt; and start building your custom abstraction, try OneSDK. We have an integration ready to &lt;a href="https://superface.ai/social-media/publishing-profiles?provider=instagram" rel="noopener noreferrer"&gt;get a list of authorized Instagram accounts&lt;/a&gt;. And the same interface works also for Facebook, LinkedIn, Pinterest, and Twitter – but let's keep it for another time.&lt;/p&gt;

&lt;p&gt;First, install OneSDK into your project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @superfaceai/one-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And paste the following code into &lt;code&gt;profiles.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SuperfaceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@superfaceai/one-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Replace the value with the token from Graph Explorer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ACCESS_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR USER ACCESS TOKEN HERE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SuperfaceClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getProfiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdkProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;social-media/publishing-profiles@1.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdkProfile&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GetProfilesForPublishing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACCESS_TOKEN&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;getProfiles&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Don't forget to insert your actual user access token as a value of the &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; variable. You can copy it from Graph API Explorer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49hq6a5bpom78qtfm8i6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49hq6a5bpom78qtfm8i6.png" alt="Graph API Explorer right sidebar with button “Copy to clipboard” next to the access token highlighted" width="644" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you run this code, you will get an array with authorized accounts, their ID, username, and profile image:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;node profiles.js
&lt;span class="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;: &lt;span class="s1"&gt;'17841455280630860'&lt;/span&gt;,
    name: &lt;span class="s1"&gt;'Dev Testing App IG Acct'&lt;/span&gt;,
    username: &lt;span class="s1"&gt;'dev_testing_app'&lt;/span&gt;,
    imageUrl: &lt;span class="s1"&gt;'https://scontent.fprg5-1.fna.fbcdn.net/...'&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The code behind integration is &lt;a href="https://github.com/superfaceai/station/tree/aee145aae8626f8736d8b8bda55cc77187b9f852/grid/social-media/publishing-profiles/maps" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; and your application communicates with Instagram API directly without intermediary servers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With the Instagram account ID, we can start managing the account via API. In the following articles I will cover publishing of images and videos, authorization flow, and retrieving posts and comments. If you don't want to miss it, &lt;a href="https://dev.to/superface/"&gt;follow Superface on Dev&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__4472"&gt;
  &lt;a href="/superface" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4472%2Fb7c584a4-1f96-4529-b014-d6a1a0c32b33.png" alt="superface image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;Superface&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;
        Superface allows GPT builders to easily connect to any API, enabling them to create, retrieve, and manage data from external platforms.
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Was this tutorial useful or did you run into any problems? Let me know in the comments! 👇&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>api</category>
      <category>instagram</category>
      <category>node</category>
    </item>
    <item>
      <title>Get started with Instagram API: The Setup</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Fri, 23 Sep 2022 15:11:09 +0000</pubDate>
      <link>https://dev.to/superface/getting-started-with-instagram-api-the-setup-2mhi</link>
      <guid>https://dev.to/superface/getting-started-with-instagram-api-the-setup-2mhi</guid>
      <description>&lt;p&gt;Instagram Graph API is Facebook's official way to access Instagram from your application. The API allows you to manage your account, publish content, and access some public data from Instagram, but only a subset of features is available compared to Instagram's official applications.&lt;/p&gt;

&lt;p&gt;That's no coincidence. Facebook severely &lt;a href="https://about.fb.com/news/2018/04/restricting-data-access/" rel="noopener noreferrer"&gt;restricted its APIs&lt;/a&gt; after the Cambridge Analytica scandal. Instagram Graph API was designed with these privacy concerns in mind, &lt;a href="https://developers.facebook.com/blog/post/2020/03/10/final-reminder-Instagram-legacy-api-platform-disabled-mar-31/" rel="noopener noreferrer"&gt;replacing former Instagram API&lt;/a&gt; which was much more open.&lt;/p&gt;

&lt;p&gt;Some design decisions and limitations around Instagram Graph API are so confusing, developers often ask how to perform even basic tasks. For example, how to get access to the API for their account, or figure out the correct account ID. I've answered numerous questions like this, which led me to start this series.&lt;/p&gt;

&lt;p&gt;This is a first dive into Instagram Graph API, covering the initial prerequisites: creating an Instagram business account and setting up a Facebook app. In the next article I will cover how to get an access token and basic information about Instagram account, including the business account ID required for further interactions. In future posts, I will cover authorization in Node.js, content publishing, and retrieval of posts and comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pair Instagram account with Facebook Page
&lt;/h2&gt;

&lt;p&gt;Instagram Graph API can be used only by “&lt;a href="https://help.instagram.com/138925576505882" rel="noopener noreferrer"&gt;Instagram Professional accounts&lt;/a&gt;” – this includes Business accounts (intended for companies), and Creator accounts (intended for individuals, like influencers). The good news is you don't need to pay anything for a Professional account.&lt;/p&gt;

&lt;p&gt;Before you proceed, you need three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Facebook account (can be your personal)&lt;/li&gt;
&lt;li&gt;A Facebook page – ideally a dedicated testing page, so &lt;a href="https://www.facebook.com/pages/create/" rel="noopener noreferrer"&gt;create one now&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An Instagram account – ideally a dedicated account for testing (&lt;a href="https://www.instagram.com/accounts/emailsignup/" rel="noopener noreferrer"&gt;sign up for one&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For development purposes, we will need to turn the &lt;a href="https://help.instagram.com/502981923235522" rel="noopener noreferrer"&gt;Instagram account into a Business account&lt;/a&gt; and pair it with the Facebook page.&lt;/p&gt;

&lt;p&gt;Once you log into your Instagram account, go to account settings. Here, click “Switch to professional account”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgb3au2t9h5azl46odsgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgb3au2t9h5azl46odsgz.png" alt="Instagram account settings with “Switch to professional account” under the left menu" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You have two options: Creator and Business. Select Business, pick a category (can be anything) and skip the step to add contact information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dx8z3ygwsmsks42b6ws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dx8z3ygwsmsks42b6ws.png" alt="Selection of Professional account type with Business selected" width="800" height="921"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, on the Facebook side, go to your Facebook page's settings. On “Professional dashboard” select “Linked Accounts” and you should see a button to “Connect account” from Instagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifdesfz4zo4yterkknu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifdesfz4zo4yterkknu7.png" alt="Dashboard with Facebook page settings with Linked Accounts highlighted in left-side navigation" width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7jaaoo8auj4fct3cnjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7jaaoo8auj4fct3cnjj.png" alt="Linked account page with Connect account highlighted" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will show you a pop-up to enter Instagram credentials, and once you log in, you should see a success message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2wa2ukqinpdfpahjuim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2wa2ukqinpdfpahjuim.png" alt="Success message: Instagram connected" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Facebook application
&lt;/h2&gt;

&lt;p&gt;Next, we will need to create a Facebook app. Visit &lt;a href="https://developers.facebook.com/apps/" rel="noopener noreferrer"&gt;My apps&lt;/a&gt; on &lt;del&gt;Facebook&lt;/del&gt; Meta for Developers site and click “Create App”. This will take you to app type selection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rftoe1h72xcajzho30i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rftoe1h72xcajzho30i.png" alt="App type selection with the first option, Business, selected" width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select “Business”, enter name and contact e-mail, and finally, you should see various products to add to your app. Find Instagram Graph API, click “Set up”, and that's it for now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznmnytcxyb1mhptcy5dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznmnytcxyb1mhptcy5dn.png" alt="Add products to app screen with Instagram Graph API highlighted" width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/superface/instagram-api-find-the-right-account-id-4k3j"&gt;next article&lt;/a&gt;, we will get to work with the API itself.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/superface" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4472%2Fb7c584a4-1f96-4529-b014-d6a1a0c32b33.png" alt="Superface" width="800" height="888"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1363%2Fc509f283-5020-499d-b2d4-ea81309b8f43.jpg" alt="" width="800" height="800"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/superface/instagram-api-find-the-right-account-id-4k3j" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Instagram API: Find the right account ID&lt;/h2&gt;
      &lt;h3&gt;Jan Vlnas for Superface ・ Sep 26 '22&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#api&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#instagram&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#node&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;If you don't want to miss it, &lt;a href="https://dev.to/superface"&gt;follow Superface on Dev&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__4472"&gt;
  &lt;a href="/superface" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4472%2Fb7c584a4-1f96-4529-b014-d6a1a0c32b33.png" alt="superface image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;Superface&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/superface" class="ltag__user__link"&gt;
        Superface allows GPT builders to easily connect to any API, enabling them to create, retrieve, and manage data from external platforms.
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Let me know if this post is useful to you and if you encounter any problems with my instructions!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Facebook is constantly changing their products, so it's possible that by the time you read this article, some flows are entirely different. You can refer to the following official resources, although in some cases these may also be outdated.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://help.instagram.com/502981923235522/" rel="noopener noreferrer"&gt;Set Up a Business Account on Instagram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://help.instagram.com/570895513091465/" rel="noopener noreferrer"&gt;Add or Change the Facebook Page Connected to Your Instagram Business Account&lt;/a&gt; – as of September 2022 this guide seems to be outdated; it describes the possibility to connect a Facebook page from Instagram side, but I didn't find the described functionality in Instagram's web interface.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.facebook.com/docs/instagram-api/getting-started" rel="noopener noreferrer"&gt;Instagram Graph API: Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>instagram</category>
      <category>api</category>
      <category>facebook</category>
    </item>
    <item>
      <title>API is like a box of chocolates, you never know what you GET</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Fri, 16 Sep 2022 14:48:44 +0000</pubDate>
      <link>https://dev.to/superface/api-is-like-a-box-of-chocolates-you-never-know-what-you-get-31p6</link>
      <guid>https://dev.to/superface/api-is-like-a-box-of-chocolates-you-never-know-what-you-get-31p6</guid>
      <description>&lt;p&gt;Imagine you get a task to integrate an API. It's a partner API, the code is outside your control, and it's not very widely used. But there is at least some documentation. You quickly dive in. There's a list of endpoints, required parameters, authorization, but something's missing. There is absolutely no information about API responses, not even how a regular response should look like, not to mention possible errors. So, it's time to pull out an HTTP client, and start poking the API with requests to see how it behaves.&lt;/p&gt;

&lt;p&gt;Dealing with undocumented responses isn't such a big deal, we have tools for visualizing JSON (for example &lt;a href="https://jsoncrack.com/" rel="noopener noreferrer"&gt;JSON Crack&lt;/a&gt;) and inferring its schema (like &lt;a href="https://jvilk.com/MakeTypes/" rel="noopener noreferrer"&gt;MakeTypes&lt;/a&gt;). It's those edge cases which completely throw off any assumptions you already made.&lt;/p&gt;

&lt;p&gt;Recently, I worked with an API which was like that. The documentation only mentioned that endpoints exist, and some particular parameters are required. Once I figured API's weird, custom-made authorization schema, I started to poke around the endpoints.&lt;/p&gt;

&lt;p&gt;First thing I noticed was responses’ content type. While the API responds with JSON, the &lt;code&gt;content-type&lt;/code&gt; header marks the response as HTML. This breaks automatic parsing in most HTTP clients (and also syntax highlighting and formatting), but the response can still be parsed manually. The fun started when some responses included HTML warnings with the data, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;Warning&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;: Undefined array key "TEST" in &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;/some/source/file&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt; on line &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;123&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
{"foo":"bar"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Technically,&lt;/em&gt; the API behaved consistently: it claimed to respond with HTML, and so it did. But in practice, this behavior renders the API mostly useless for consumers. Sure, I could search the response and parse only the JSON part. While finding clever hacks is fun, it's usually not sustainable in the long run. Not to mention that keeping error messages exposing your source files structure is a &lt;a href="https://owasp.org/www-community/attacks/Full_Path_Disclosure" rel="noopener noreferrer"&gt;security risk&lt;/a&gt;. I reported the issue to the API provider (somehow surprised no one reported this issue before). They were swift to respond and improved their API based on my suggestions.&lt;/p&gt;

&lt;p&gt;While I love to solve technical challenges as any other developer, sometimes it's really easier to talk to people. However, it frustrates me to think how much time is wasted by examining on poorly documented APIs with surprising behaviors. And while we should be advocating for better developer experience even for internal and partner APIs, we can also share our discoveries by abstracting APIs into &lt;a href="https://dev.to/superface/stop-the-manual-api-plumbing-5639"&gt;reusable use cases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now to you: &lt;strong&gt;What was the weirdest API behavior you dealt with?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Cover photo credit: &lt;a href="https://www.pexels.com/photo/chocolate-cake-box-352876/" rel="noopener noreferrer"&gt;Lukas on Pexels&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>devjournal</category>
      <category>discuss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>“Look ma, no config file!” Introducing OneSDK 2.0</title>
      <dc:creator>Jan Vlnas</dc:creator>
      <pubDate>Tue, 16 Aug 2022 13:08:00 +0000</pubDate>
      <link>https://dev.to/superface/look-ma-no-config-file-introducing-onesdk-20-40on</link>
      <guid>https://dev.to/superface/look-ma-no-config-file-introducing-onesdk-20-40on</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/superfaceai/one-sdk-js"&gt;OneSDK&lt;/a&gt; is a universal client for consuming API integrations. It is a core component of the Superface ecosystem — whether you pick an &lt;a href="https://superface.ai/catalog"&gt;existing integration&lt;/a&gt;, or decide to build one yourself.&lt;/p&gt;

&lt;p&gt;Today, we are excited to announce OneSDK v2.0.0, a new major version which simplifies the usage of Superface integrations and reduces the memory footprint. If you are using OneSDK in your application, pay attention to the breaking changes and check out the &lt;a href="https://superface.ai/docs/upgrade/one-sdk-v2"&gt;upgrade guide&lt;/a&gt;. But first, let’s dive into the new features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use OneSDK without configuration
&lt;/h2&gt;

&lt;p&gt;The most significant change in this release is simplified use of integrations published in the &lt;a href="https://superface.ai/catalog"&gt;Superface registry&lt;/a&gt;. Previously, you had to use the Superface CLI (&lt;code&gt;@superfaceai/cli&lt;/code&gt; package) to install the integration profile and configure providers. This updated the &lt;code&gt;super.json&lt;/code&gt; configuration file and saved &lt;code&gt;.supr&lt;/code&gt; files into local project.&lt;/p&gt;

&lt;p&gt;With OneSDK v2, these steps are not required anymore. To start using Superface integrations, pick one from the &lt;a href="https://superface.ai/catalog"&gt;catalog&lt;/a&gt;, install the &lt;code&gt;@superfaceai/one-sdk&lt;/code&gt; package, and use the provided example code.&lt;/p&gt;

&lt;p&gt;As an example, let’s display a &lt;a href="https://superface.ai/weather/forecast-city"&gt;weather forecast&lt;/a&gt; with my favorite weather service, &lt;a href="https://wttr.in/"&gt;wttr.in&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, install OneSDK with npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @superfaceai/one-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And paste the following code into a &lt;code&gt;weather.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SuperfaceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@superfaceai/one-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Change to your city!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New York City, NY, USA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SuperfaceClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;showWeather&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weather/forecast-city@1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GetWeatherForecastInCity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wttr-in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;showWeather&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run &lt;code&gt;node weather.js&lt;/code&gt;, you should get a temperature forecast for the next three days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;averageTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2022-08-11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;averageTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2022-08-12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;averageTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2022-08-13&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minTemperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OneSDK v2 will fetch and cache profiles at runtime, which means &lt;code&gt;super.json&lt;/code&gt; configuration is no longer necessary. If you already use OneSDK with &lt;code&gt;super.json&lt;/code&gt;, you don’t need to change anything. The &lt;code&gt;super.json&lt;/code&gt; file acts as a central place for locking profile versions and decoupling providers configuration from the code. It is also needed for using local profiles and maps (see breaking changes below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Pass security values at runtime
&lt;/h2&gt;

&lt;p&gt;While OneSDK doesn’t need a configuration file, many providers require an API token or another form of authentication. Previously, we used &lt;a href="https://superface.ai/blog/changelog-november-2021#provider-integration-parameters"&gt;integration parameters&lt;/a&gt; for passing provider-specific values at runtime, such as OAuth access tokens. Now you can also use security values in the &lt;code&gt;perform&lt;/code&gt; method. Pass in a &lt;code&gt;security&lt;/code&gt; option with required security values; here is an example for sending an email with SendGrid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;communication/send-email@2.1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Use the profile&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SendEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-reply@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jane.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your order has been shipped!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello Jane, your recent order on Our Shop has been shipped.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendgrid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;bearer_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your token from sendgrid&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The property name (&lt;code&gt;bearer_token&lt;/code&gt; in this example) of the security value is provider-specific. The code examples in the catalog will help you figure out what values need to be passed in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fewer dependencies, less memory usage
&lt;/h2&gt;

&lt;p&gt;We want OneSDK to be usable everywhere you can run Node.js, regardless of resource constraints. Previous versions required a Comlink parser, which in turn depended on the TypeScript package at runtime. In OneSDK v2, we have removed the parser, resulting in a leaner package with a smaller memory footprint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbfopd0axng2hbioqsxn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbfopd0axng2hbioqsxn.png" alt="Screenshot of two memory usage charts. First shows OneSDK 1.5.2 using 61 MB of heap, the second shows OneSDK 2.0.0 using 31 MB of heap" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;


With a single profile and a single map, OneSDK v2.0.0 consumes 30 MB less memory than OneSDK v1.5.2 &lt;a href="https://github.com/jnv/one-sdk-benchmark"&gt;(benchmark code)&lt;/a&gt;




&lt;p&gt;Since the parser is no longer included in OneSDK, local maps and profiles need to be compiled with Superface CLI. Read below about this breaking change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking changes
&lt;/h2&gt;

&lt;p&gt;Change in a major version implies breaking changes, which is also the case for OneSDK v2. The most significant breaking change is the removal of the Comlink parser, which affects OneSDK users with local profile and map files.&lt;/p&gt;

&lt;p&gt;We have also changed the default cache location and reduced the usage of the &lt;code&gt;superface/&lt;/code&gt; directory. These changes (hopefully) won’t break your application, but can help you clean up the previously generated files in your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local profiles and maps require a compile step
&lt;/h3&gt;

&lt;p&gt;If your project depends on local profiles or maps, OneSDK v2 won’t automatically parse them anymore, and throws an error during initialization. To see if you are affected, check the &lt;code&gt;superface/super.json&lt;/code&gt; for &lt;code&gt;"file"&lt;/code&gt; properties, for example, the following configuration depends on a local profile (&lt;code&gt;my-profile.supr&lt;/code&gt;) and a map (&lt;code&gt;my-profile.my-provider.suma&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profiles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./my-profile.supr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;providers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-provider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./my-profile.my-provider.suma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OneSDK will look for compiled &lt;code&gt;*.ast.json&lt;/code&gt; files to load them at runtime. Run &lt;code&gt;npx @superfaceai/cli@latest compile&lt;/code&gt; in your project to compile the source profiles and maps. We recommend adding a compilation step to your existing build step or to run it at the application start. More details are available in the &lt;a href="https://superface.ai/docs/upgrade/one-sdk-v2"&gt;upgrade guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean up of &lt;code&gt;superface/&lt;/code&gt; directory
&lt;/h3&gt;

&lt;p&gt;With the use of &lt;code&gt;super.json&lt;/code&gt; configuration becoming optional, and the removal of local &lt;code&gt;.supr&lt;/code&gt; files, we took the first steps towards phasing out the &lt;code&gt;superface/&lt;/code&gt; directory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cache directory location changed from &lt;code&gt;superface/.cache&lt;/code&gt; to &lt;code&gt;node_modules/.cache/superface&lt;/code&gt;. &lt;code&gt;superface/.cache&lt;/code&gt; directory can be removed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;superface/grid&lt;/code&gt; directory can be removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find more detail in the &lt;a href="https://superface.ai/docs/upgrade/one-sdk-v2"&gt;upgrade guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tell us your feedback
&lt;/h2&gt;

&lt;p&gt;We are eager to hear your feedback about our latest release. Join us on the &lt;a href="https://sfc.is/discord"&gt;Superface Discord server&lt;/a&gt; to tell us what you are building, or hit us up on &lt;a href="https://twitter.com/superfaceai"&gt;Twitter&lt;/a&gt;. If you run into any issues, report them in the &lt;a href="https://github.com/superfaceai/one-sdk-js"&gt;OneSDK repository&lt;/a&gt;. Or just &lt;a href="https://superface.ai/docs/support"&gt;reach out to us&lt;/a&gt; — we are happy to help!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>api</category>
      <category>node</category>
      <category>news</category>
    </item>
  </channel>
</rss>
