Search code examples
phpajaxbitcoincoinbase-phpcoinbase-api

How do I interact with Coinbase's API? Why does it always fail?


Accessing the Coinbase API used to be really simple: all you needed was an API key. Now you need something called a "nonce" and a "signature". I pass my new API "Secret", the nonce, and the Key in my request, but it returns an "invalid_key" error. What gives?

EDIT March 12th: Added a tutorial on interacting with the API via OAuth.


Solution

  • YE GREAT OAUTH TUTORIAL

    <?php
    
    /*OAuth is great. It's also complicated. Or rather, it LOOKS complicated.
    
    This whole script is just one big long function. It's a really, really ugly
    function. I broke down everything "Barney-style" to demonstrate all the steps
    in the process, and because there are some things you have to decide -- how to
    record the user data, for instance.
    
    Let's get this train wreck a rollin'.*/
    
    function oauthRequest($apiPath,$getOrPost,$parameters){
    
    /*You get this info from https://coinbase.com/oauth/applications*/
    $clientId = "#####";
    $clientSecret = "#####";
    $callbackUrl = "http://www.blah.com/oauth.php";
    
    function curling($url,$getpost,$params){
        if($params != ""){
            $params = http_build_query(json_decode($params), true);
        }
        if($getpost == "get"){
            $ispost = false;
            $url .= $params;
        }
        $ch = curl_init();
        curl_setopt_array($ch, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ));
        if($getpost == "post"){
            curl_setopt_array($ch, array(
                CURLOPT_POST => $ispost,
                CURLOPT_POSTFIELDS => $params
            ));
        }
        $results = curl_exec($ch);
        curl_close($ch);
        return $results;
    }
    
    /*There are two people involved here: the Client (you), and the User (the
    person accessing your app or site).
    
    You'll need 3 pieces of data for each user before your app can access their
    Coinbase account: a User Code, an Access Token, and a Refresh Token.
    
    For the purposes of this demonstration, I'm recording all of the user data in
    a .txt file on my server. THIS IS PROBABLY A BAD IDEA in real life because .txt
    files aren't secure at all, and this text file will only store the data for one
    user (it gets overwritten every time). This is the kind of stuff you'd put in an
    SQL database if you have one, or maybe in the user's cookies.*/
    
    if(!file_exists("userdata.txt") || file_get_contents("userdata.txt") == ""){
        file_put_contents("userdata.txt",json_encode(array(
            "userCode" => "",
            "accessToken" => "",
            "refreshToken" => ""
        )), LOCK_EX);
    }
    $userData = json_decode(file_get_contents("userdata.txt"), true);
    
    /*Okay. So. First thing we're going to do is see if we have a User Code for
    this user. These are big alphanumeric strings that are 64 characters long. If
    we have one, it'll either be in the URL of this page (the $_GET array), or
    it'll be in that userdata.txt file.*/
    
    if(array_key_exists("code",$_GET) && $_GET["code"] != ""){
        $userCode = $_GET["code"];
    }else if(array_key_exists("userCode",$userData) && $userData["userCode"] != ""){
        $userCode = $userData["userCode"];
    }else{
    
    /*If we don't have a User Code, then this next snippet of code will fire. It'll
    return the link for a special user-specific Coinbase page to which the user
    will need to go to authorize your app to access their Coinbase account (by
    signing into Coinbase and clicking a green "Authorize" button).
    
    After authorizing your app, they'll be automatically taken to the Redirect URL
    you specified, with their User Code added to the end of the URL. So if your
    Redirect URL is www.blah.com/oauth.php, they'll go to www.blah.com/oauth.php?
    code=123451234512345 .
    
    This User Code never expires, and so theoretically the user should only need to
    go to the authorization link once. However, if you don't make a way of getting
    the User Code in the future (my fancy "userdata.txt" in this case) or they de-
    authorized your app from within their Coinbase account, then they'll need to go
    to the link again and re-authorize your app from the beginning.
    
    I have it set up so my Redirect URL and the rest of my OAuth script are all on
    the same page: www.blah.com/oauth.php . So the user will just start back at the
    beginning of this script, and THIS time the script will see the User Code in
    the URL (the $_GET array), and so will skip this next bit.
    
    Whew. You with me so far?*/
        return ("https:/*coinbase.com/oauth/authorize?" . http_build_query(array(
            "response_type" => "code",
            "client_id" => $clientId,
            "redirect_uri" => $callbackUrl
        )));
        die;
    }
    
    /*Here I am, recording the User Code for future use in userdata.txt*/
    $userData["userCode"] = $userCode;
    file_put_contents("userdata.txt",json_encode($userData),LOCK_EX);
    
    /*Alright, we've got the User Code. Now we need the Access Token -- another 64-
    character string. The difference is that the Access Token expires every 2 hours
    (7200 seconds). Let's see if we already have one in the userdata.txt file.*/
    if(array_key_exists("accessToken",$userData) && $userData["accessToken"] != ""){
        $accessToken = $userData["accessToken"];
        $refreshToken = $userData["refreshToken"];
    }else{
    
    /*If we're here, it's because we don't have an Access Token for this user. We
    get one by making this POST request:*/
        $authorization = json_decode(curling(
            "https:/*coinbase.com/oauth/token" . "?" . http_build_query(array(
                "grant_type" => "authorization_code",
                "code" => $userCode,
                "redirect_uri" => $callbackUrl,
                "client_id" => $clientId,
                "client_secret" => $clientSecret
            )), "post", ""), true);
        if(array_key_exists("error",$authorization)){
    
    /*If something goes wrong here, I'm going to clean out userdata.txt and ask the
    user to try again.*/
            file_put_contents("userdata.txt","",LOCK_EX);
            die("Something went wrong. Please refresh the page and try again.");
        }
        $accessToken = $authorization["access_token"];
        $refreshToken = $authorization["refresh_token"];
    }
    
    /*The Refresh Token is what you use to get a new Access Token once the current
    Access Token has expired. The Refresh Token never expires, but can only be used
    once. Anytime you get an Access Token, you'll also be given a Refresh Token.
    
    If you don't have the Refresh Token and a working Access Token for the user,
    they'll need to re-authorize your app all over again.
    
    I'm backing up the Access Token and Refresh Token to userdata.txt*/
    $userData["accessToken"] = $accessToken;
    $userData["refreshToken"] = $refreshToken;
    file_put_contents("userdata.txt",json_encode($userData),LOCK_EX);
    
    /*Alright! At this point, we should have the three bits of user data we need:
    the User Code, the Access Token, and the Refresh Token. So now lets try
    actually making an API request.
    
    This whole script is really just one big function called "oauthRequest". You
    pass three parameters to the function: the path of the API request (everything
    after https:/*coinbase.com/api/v1/), whether this API query is a GET or a POST,
    and any parameters that go along with that GET or POST request. These params
    first come into play here.
    
    Let's make the API request:*/
    $results = curling("https:/*coinbase.com/api/v1/" . $apiPath . "?" . http_build_query(array(
            "access_token" => $accessToken
        )), $getOrPost, $parameters);
    
    /*Now we're going to make sure the request actually worked, and didn't get
    rejected because the Access Token was expired. If it WAS expired, the
    results should be blank. (It'll return a 401 if you want to get fancy.)*/
    $resultsArray = json_decode($results);
    if(count($resultsArray) < 1){
    
    /*Looks like it did expire, so now we make a POST request using the Refresh
    token, which will return a new Access Token AND a new Refresh Token.*/
        $reAuthorization = json_decode(curling(
            "https:/*coinbase.com/oauth/token?" . http_build_query(array(
                "grant_type" => "refresh_token",
                "code" => $userCode,
                "refresh_token" => $refreshToken
            )), "post", ""), true);
        $accessToken = $reAuthorization["access_token"];
        $refreshToken = $reAuthorization["refresh_token"];
    
    /*Let's back those up to userdata.txt...*/
        $userData["accessToken"] = $accessToken;
        $userData["refreshToken"] = $refreshToken;
        file_put_contents("userdata.txt",json_encode($userData),LOCK_EX);
    
    /*...and try the API request all over again:*/
        $results = curling("https:/*coinbase.com/api/v1/" . $apiPath . "?" . http_build_query(array(
                "access_token" => $accessToken
            )), $getOrPost, $parameters);
    
    /*If it doesn't work THIS time, I'm going to clean out userdata.txt and ask
    the user to try again. One of the codes probably got all mungled up.*/
        $resultsArray = json_decode($results);
        if(array_key_exists("error",$resultsArray)){
            file_put_contents("userdata.txt","",LOCK_EX);
            die("Something went wrong. Please refresh the page and try again.");
        }
    }
    
    /*If, however, everything went right, then this function will return the JSON
    string with the data from the API! Hooray!*/
    return $results;
    
    }
    
    /*Here are 4 different example requests you can make.*/
    
    /*
    echo oauthRequest("account/generate_receive_address","post","");
    
    echo oauthRequest("buttons","post",'{
        "button": {
            "name": "test",
            "type": "buy_now",
            "price_string": ".01",
            "price_currency_iso": "USD"
        }
    }');
    
    echo oauthRequest("prices/buy","get",'{
        "qty": 1,
        "currency": "USD"
    }');
    
    echo oauthRequest("account/balance","get","");
    */
    
    ?>