phpgithubcurl

Message: "Not Found" (404) when trying to create private repo on github /user/repos


I'm getting error:

{
    "message":"Not Found",
    "documentation_url":"https://docs.github.com/rest",
    "status":"404"
}

when trying to create private repo on my GitHub account. My code in php:

$curl = curl_init();

curl_setopt_array($curl, array(
    CURLOPT_URL => 'https://api.github.com/MY_USERNAME/repos',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => '',
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => array(
        "Accept: application/vnd.github+json",
        "Authorization: Bearer MY_TOKEN",
        "X-GitHub-Api-Version: 2022-11-28",
        "User-Agent: MY_APP_NAME"
    ),
    CURLOPT_POSTFIELDS => array(
        "name" => "test-repo-123",
        "description" => "This is your first repo!",
        "homepage" => "https://MY_URL",
        "private" => true,
        "is_template" => true
    ),
));

$response = curl_exec($curl);

curl_close($curl);

I had to create GH App, and installed it on my GH Account.

When I cut line:

"User-Agent: MY_APP_NAME"

I get error:

Request forbidden by administrative rules. Please make sure your request has a User-Agent header (https://docs.github.com/en/rest/overview/resources-in-the-rest-api#user-agent-required). Check https://developer.github.com for other possible causes.

Ofcourse, in constants: MY_USERNAME, MY_TOKEN, MY_APP_NAME, MY_URL I have my sensitive data.

What am I doing wrong?

Thanks Mike

POST UPDATE

I have correct the code. But now I get such error:

{
    "message":"Not Found",
    "documentation_url":"https://docs.github.com/rest/repos/repos#create-a-repository-for-the-authenticated-user",
    "status":"404"
}

I've modified code, by gaining token from github oauth:

Route::get('/route1', function () {
    if (Session::get('authorization') == '1') {
        Session::put('authorization', '2');

        $code = request()->input('code');

        if (!$code) {
            return abort(404);
        }
    
        $res = Http::post('https://github.com/login/oauth/access_token', [
            'client_id' => 'MY_CLIENT_ID',
            'client_secret' => 'MY_SECRET',
            'code' => $code,
            'redirect_uri' => 'https://MY_URL/route1',
        ]);
    
        $query_str = $res->body();
        $data = [];
        parse_str($query_str, $data);

        Session::forget('authorization');
        if (!isset($data['access_token'])) {
            return abort(404);
        }
        
        $curl = curl_init();
    
        curl_setopt_array($curl, array(
            CURLOPT_URL => 'https://api.github.com/user/repos',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => 'POST',
            CURLOPT_HTTPHEADER => array(
                "Accept: application/vnd.github+json",
                "Authorization: Bearer {$data['access_token']}",
                "X-GitHub-Api-Version: 2022-11-28",
                "User-Agent: TryfonEuAuth"
            ),
            CURLOPT_POSTFIELDS => array(
                "name" => "my-new-repo-name",
                "description" => "This is your first repo!",
                "homepage" => "https://MY_HOMEPAGE",
                "private" => true,
                "is_template" => true
            ),
        ));
    
        $response = curl_exec($curl);
    
        curl_close($curl);
    
        Log::info("aa", [$response]);
    
        dd($response);
    }
});

Route::get('/oauth', function () {
    Session::put('authorization', '1');
    return redirect('https://github.com/login/oauth/authorize?client_id=MY_CLIENT&state=MY_HASH');
})->middleware('auth.basic');

Whats wrong now?

SUCCESS SOLUTION

Like Allan said, the misstake was, that body need to be RAW Json! Now its ok. I don't need to connect with outh. I just generated 30day valid token in my profile panel GitHub.

Below I put working code:

$res = Http::withHeaders([
        'Authorization' => 'Bearer MY_TOKEN',
        'Content-Type' => 'application/json',
        'User-Agent' => 'Laravel/11.31',
    ])->withBody(
'{
"name":"my-test-repo-2",
"description":"test repo 2!",
"homepage":"https://github.com",
"private":true,
"is_template":true
}'
    )->post('https://api.github.com/user/repos');

Solution

  • From what I've found, your URL shouldn't contain "MY_USERNAME" (your username) but thw word "user" instead.

    // FROM
    CURLOPT_URL => 'https://api.github.com/MY_USERNAME/repos',
    
    // TO
    CURLOPT_URL => 'https://api.github.com/user/repos',
    

    Here GitHub made an endpoint for managing repos for user, so it's like a placeholder to precise you create a repo for the authenticated user.

    Hope this helps!

    EDIT POST UPDATE

    From your updated code and based on GitHub's documentation, you're giving a body in an array format but GitHub's api asks for a json encoded one. In your CURLOPT_HTTPHEADER add the following line that specifies that the body is encoded in json :

    CURLOPT_HTTPHEADER => array(
        "Accept: application/vnd.github+json",
        "Authorization: Bearer {$data['access_token']}",
        "X-GitHub-Api-Version: 2022-11-28",
        "User-Agent: TryfonEuAuth",
        "Content-Type: application/json"
    ),
    

    Also, to encode your body in JSON, in your line CURLOPT_POSTFIELDS => array([ replace array by json_encode. This way, you'd get this modified header and body:

    CURLOPT_HTTPHEADER => array(
        "Accept: application/vnd.github+json",
        "Authorization: Bearer {$data['access_token']}",
        "X-GitHub-Api-Version: 2022-11-28",
        "User-Agent: TryfonEuAuth",
        "Content-Type: application/json"
    ),
    CURLOPT_POSTFIELDS => json_encode([
        "name" => "my-new-repo-name",
        "description" => "This is your first repo!",
        "homepage" => "https://MY_HOMEPAGE",
        "private" => true,
        "is_template" => true
    ]),