Update: About 6 weeks after I posted this, Facebook announced they're deprecating the Instagram API in favor of the Instagram Graph API (perhaps I'll try that one out sometime). If you use this API, expect to frequently see errors like this one:

{
    "meta": {
        "code": 400,
        "error_type": "APINotAllowedError",
        "error_message": "This endpoint has been retired"
    }
}

Continuing the 15 APIs in 15 Days challenge (also on GitHub), I think we'll stick with the social media theme for now and check out the Instagram API.

First though, two things to consider:

  • If you're unfamiliar with APIs, read this first to familiarize yourself with the concept.
  • Install Postman, which allows you to access API endpoints without having to write an app.

Authenticating

Like the Twitter API we looked at yesterday, the Instagram API also requires authenticating (proving you're a valid Instagram member) before you can use it. Where the Twitter API uses OAuth 1.0 though, the Instagram API uses OAuth 2.0. We'll see how that differs in practice.

Every request requires the auth access token be included as a query parameter, and trying to make a request (like requesting metadata on an image) without one returns an error instead.

instagram-api---no-auth-1

Generate a Client ID

Starting at the start, click the "Register Your Application" button, and then on the next page click "Register a New Client". Normally we'd be using the API for a legitimate app we're trying to create, and this would be our chance to tell Instagram about it. Instead, just fill in a bunch of bogus details - we're just experimenting after all.

instagram-api---creating-app

If all goes well, you should be taken to a screen that confirms your "app" has been setup, and you're presented with a "client id". Hang on to that. If your browser closes or something, just go back to the Manage Clients dashboard.

instagram-api---app-created

Enable Implicit OAuth

There are two ways to generate an access token - one is more secure but has extra steps, while the other is good when your app has no server-side component. Since we're just experimenting here, go back to the Manage Client screen and uncheck the "Disable implicit OAuth" checkbox.

instagram-api---enable-implicit-auth

Doing this allows us to generate an access token in an easier way without getting an error like this one:

{
    "error_type": "OAuthForbiddenException",
    "code": 403,
    "error_message": "Implicit authentication is disabled"}

Generate an Access Token

Having the client id is only halfway there. If you try to use that in an API call, you'll just get an error like "The access_token provided is invalid". Because well... it's invalid.

The next step is to pass the client id to Instagram's authorization url, in order to generate an access token. The redirect URI has to match what you entered when you created your "app", otherwise the only response you'll get is something like this, which thankfully is at least very specific.

{
    "error_type": "OAuthException",
    "code": 400,
    "error_message": "Redirect URI does not match registered redirect URI"}

So plug the correct values into the following URL (grab the client id from the Manage Client screen) and just paste it into your browser address bar. Replace REDIRECT-URI with "http://localhost" if you entered "localhost" like I did when you created the app.

https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token

For a legitimate app, you'd want to use a legitimate website you control for the REDIRECT-URI, so you could read the return value and do what you need with it. In our case, all we want is to have the token pasted in our address bar, so it's perfectly fine to tell Instagram to just redirect to localhost... even if you don't have a website running on your machine.

Your browser should be directed to the URI you specified, and it should have your access token appended after it. The page itself might say something like "the site can't be reached" - just check the address bar.

http://localhost/#access_token=<your-new-access-token>

Try an API call again

Now pick a photo - one you've uploaded or someone else's that's public - and try accessing its metadata. I don't have too many photos on Instagram, so I picked one I took of our garden over the summer.

Snap peas in our garden, summer 2017

A post shared by Grant (@grantwinney) on

Here's what I put into Postman, and the response I got back. Pretty cool, huh? There's a ton of info, from the caption and numbers of likes and comments, to stuff I can't see through the website like URLs for various image sizes.

GET https://api.instagram.com/v1/media/shortcode/Bcizg1_B3nu?access_token=<my_access_token>

{
    "data": {
        "id": "1667121369441597934_3657782666",
        "user": {
            "id": "3657782666",
            "full_name": "Grant",
            "profile_picture": "https://instagram.fymy1-2.fna.fbcdn.net/t51.2885-19/11906329_960233084022564_1448528159_a.jpg",
            "username": "grantwinney"
        },
        "images": {
            "thumbnail": {
                "width": 150,
                "height": 150,
                "url": "https://scontent.cdninstagram.com/t51.2885-15/s150x150/e35/25015742_1019539261522471_6659134500804493312_n.jpg"
            },
            "low_resolution": {
                "width": 320,
                "height": 320,
                "url": "https://scontent.cdninstagram.com/t51.2885-15/s320x320/e35/25015742_1019539261522471_6659134500804493312_n.jpg"
            },
            "standard_resolution": {
                "width": 640,
                "height": 640,
                "url": "https://scontent.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/25015742_1019539261522471_6659134500804493312_n.jpg"
            }
        },
        "created_time": "1512956375",
        "caption": {
            "id": "17899911943100617",
            "text": "Snap peas in our garden, summer 2017",
            "created_time": "1512956375",
            "from": {
                "id": "3657782666",
                "full_name": "Grant",
                "profile_picture": "https://instagram.fymy1-2.fna.fbcdn.net/t51.2885-19/11906329_960233084022564_1448528159_a.jpg",
                "username": "grantwinney"
            }
        },
        "user_has_liked": false,
        "likes": {
            "count": 0
        },
        "tags": [],
        "filter": "Normal",
        "comments": {
            "count": 0
        },
        "type": "image",
        "link": "https://www.instagram.com/p/Bcizg1_B3nu/",
        "location": null,
        "attribution": null,
        "users_in_photo": []
    },
    "meta": {
        "code": 200
    }
}

What else can we do?

Once you're authorized, you can make a lot of other requests as defined in their API Endpoints documentation.

Information about you!

Well, me. You can request information about the current user attached to the access token:

GET https://api.instagram.com/v1/users/self?access_token=<my_access_token>

{
    "data": {
        "id": "3657782666",
        "username": "grantwinney",
        "profile_picture": "https://instagram.fsaw1-7.fna.fbcdn.net/t51.2885-19/11906329_960233084022564_1448528159_a.jpg",
        "full_name": "Grant",
        "bio": "",
        "website": "https://grantwinney.com/",
        "is_business": false,
        "counts": {
            "media": 7,
            "follows": 21,
            "followed_by": 16
        }
    },
    "meta": {
        "code": 200
    }
}

Get metadata about the "Likes" for an image

I'll try an example using the previous photo's ID (1667121369441597934_3657782666) which got returned along with the rest of the image metadata.

GET https://api.instagram.com/v1/media/1667121369441597934_3657782666/likes?access_token=<my_access_token>

{
    "data": [
        {
            "id": "3657782666",
            "username": "grantwinney",
            "full_name": "Grant",
            "profile_picture": "https://instagram.frir1-1.fna.fbcdn.net/t51.2885-19/11906329_960233084022564_1448528159_a.jpg"
        }
    ],
    "meta": {
        "code": 200
    }
}

Hey, look at this. Someone liked my photo! Oh. It's me.

Requesting more access / scope / permission

What happens if we try to delete a "Like" though? Well, then we get an error message like this one, because we didn't explicitly request permission to do that when we got the access token.

DELETE https://api.instagram.com/v1/media/1667121369441597934_3657782666/likes?access_token=<my_access_token>

{
    "meta": {
        "code": 400,
        "error_type": "OAuthPermissionsException",
        "error_message": "This request requires scope=likes, but this access token is not authorized with this scope. The user must re-authorize your application with scope=likes to be granted this permissions."
    }
}

Instagram calls them "scopes", but it's the same as permissions - you don't want everyone to be able to do everything, so you only give them what they need.

Let's try using the authorization url in the browser again, but this time with additional scopes tacked on to the end of it. In fact, I'm just going to request everything.

https://api.instagram.com/oauth/authorize/?client_id=<my_client_id>&redirect_uri=http://localhost&response_type=token&scope=public_content+follower_list+comments+relationships+likes

instagram-api---auth-request-with-scopes

Press the "Authorize" button and you'll be redirected to localhost again. The address bar should have the same token as before, so go back to Postman and try deleting the "Like" again. When I did, it worked this time. I checked the image on instagram.com to be sure, and it was gone.

{
    "data": null,
    "meta": {
        "code": 200
    }
}

Thoughts

I gotta say, both the Twitter API and the Instagram API are very well documented. They have nice guides that step you through everything you need to do, and they're pretty easy to read. Documentation is usually the thing that suffers first on a lot of projects.

I like that Instagram has a concept of scopes so you only grant the required access. I'm confused why the scopes page says "(applications no longer accepted)" next to every scope except basic though. It sounds like all the scopes work fine in sandbox mode or for personal apps, but for anything else being used in production, your app needs to be reviewed and approved for the requested permissions, which depends on how your app is being used.

I guess this hands-on approach leads to more secure apps, but then it's got to be time-consuming to review them all... which is probably why they're not accepting applications! Kinda weird. It really seems to shut the door on anyone creating applications that build on the Instagram platform.