@@ -11,7 +11,13 @@ kick the tires than [cURL][curl].
11
11
12
12
###Hello World
13
13
14
- Let start by` GET ` ting Chris Wanstrath's GitHub profile:
14
+ Let's start by testing our setup:
15
+
16
+ curl https://api.github.com/zen
17
+
18
+ Keep it logically awesome.
19
+
20
+ Next, let's` GET ` Chris Wanstrath's GitHub profile:
15
21
16
22
# GET /users/defunkt
17
23
curl https://api.github.com/users/defunkt
@@ -93,7 +99,7 @@ earlier, you should see a `plan` object on the response:
93
99
94
100
While convenient, Basic Auth isn't ideal because you shouldn't give your GitHub
95
101
username and password to anyone. Applications that need to read or write
96
- private information using the API on behalf of another user should use[ OAuth] [ oauth ] .
102
+ private information using the API on behalf of another user should use[ OAuth] [ oauth ] .
97
103
98
104
Instead of usernames and passwords, OAuth uses_ tokens_ . Tokens provide two big
99
105
features:
@@ -105,13 +111,43 @@ authorize a third party app.
105
111
Normally, tokens are created via a[ web flow] [ webflow ] , where third party
106
112
applications send users to GitHub to log in and authorize their application and
107
113
GitHub redirects the user back to the third party application. You don't need
108
- to set up the entire web flow to begin working with OAuth tokens. The[ Authorizations API] [ authorizations api ]
114
+ to set up the entire web flow to begin working with OAuth tokens. The[ Authorizations API] [ authorizations api ]
109
115
makes it simple to use Basic Auth to create an OAuth token.
110
116
111
117
curl -i -u pengwynn \
112
118
-d '{"scopes": ["repo"]}' \
113
119
https://api.github.com/authorizations
114
120
121
+ HTTP/1.1 201 Created
122
+ Server: nginx/1.0.14
123
+ Date: Wed, 14 Nov 2012 14:04:24 GMT
124
+ Content-Type: application/json; charset=utf-8
125
+ Connection: keep-alive
126
+ Status: 201 Created
127
+ Cache-Control: max-age=0, private, must-revalidate
128
+ X-Content-Type-Options: nosniff
129
+ ETag: "f6a0ce8bac4559f2a578afd9dad51f95"
130
+ X-GitHub-Media-Type: github.beta
131
+ Location: https://api.github.com/authorizations/2
132
+ Content-Length: 384
133
+
134
+ {
135
+ "scopes": [
136
+ "repo"
137
+ ],
138
+ "token": "5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4",
139
+ "updated_at": "2012-11-14T14:04:24Z",
140
+ "url": "https://api.github.com/authorizations/2",
141
+ "app": {
142
+ "url": "http://developer.github.com/v3/oauth/#oauth-authorizations-api",
143
+ "name": "GitHub API"
144
+ },
145
+ "created_at": "2012-11-14T14:04:24Z",
146
+ "note_url": null,
147
+ "id": 2,
148
+ "note": null
149
+ }
150
+
115
151
There's a lot going on in this one little call, so let's break it down. First,
116
152
the` -d ` flag indicates we're doing a` POST ` , using the
117
153
` application/x-www-form-urlencoded ` content type. All` POST ` requests to the
@@ -124,19 +160,252 @@ we're setting up the token with _repo_ access, the most permissive scope in the
124
160
GitHub API, allowing access to read and write to private repositories. See[ the
125
161
docs] [ scopes ] for a full list of scopes.
126
162
163
+ The` 201 ` status tells us the call was successful and the JSON returned
164
+ contains the details of our new OAuth token. Now we can use the forty character
165
+ ` token ` instead of username and password in the rest of our examples. Let's
166
+ grab our own user info again, using OAuth this time:
167
+
168
+ curl -i -H 'Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4' \
169
+ https://api.github.com/user
170
+
171
+ A quick note about tokens.** Treat OAuth tokens like passwords.**
172
+ Don't share them with other users or store them in insecure places. The tokens
173
+ in these examples are fake and the names have been changed to protect the
174
+ innocent.
175
+
176
+ Now that we've got the hang of making authenticated calls, let's move along to
177
+ the[ Repositories API] [ repos-api ] .
178
+
127
179
##Repositories
128
180
129
- ##Commits
181
+ Most any meaningful use of the GitHub API will involve some level of Repository
182
+ information. We can` GET ` repository details in the same way we fetched user
183
+ details earlier:
184
+
185
+ curl -i https://api.github.com/repos/twitter/bootstrap
186
+
187
+ In the same way, we can view repositories for the authenticated user:
188
+
189
+ curl -i -H 'Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4' \
190
+ https://api.github.com/user/repos
191
+
192
+ ... list repositories for another user:
193
+
194
+
195
+ curl -i https://api.github.com/users/technoweenie/repos
196
+
197
+ ... or list repositories for an organization:
198
+
199
+ curl -i https://api.github.com/orgs/github/repos
200
+
201
+ The information returned from these calls will depend on how we authenticate:
202
+
203
+ * Using Basic Auth, everything the user has access to see on github.com
204
+ * Using OAuth, private repositories are only returned if the OAuth token contains 'repo' scope.
205
+
206
+ As the[ docs] [ repos-api ] indicate, these methods take a` type ` parameter that
207
+ can filter the repositories returned based on what type of access the user has
208
+ for the repository. In this way we can fetch only directly-owned repositories,
209
+ organization repositories, or repositories the user collaborates on via a
210
+ team.
211
+
212
+ curl -i "https://api.github.com/users/technoweenie/repos?type=owner"
213
+
214
+ In this example, we can grab only those repositories that Rick owns, not the
215
+ ones on which he collaborates. Note the quoted URL above. Depending on your
216
+ shell setup, cURL sometimes requires a quoted URL or else it ignores the
217
+ querystring.
218
+
219
+ ###Create a repository
220
+
221
+ Fetching information for existing repositories is a common use case, but the
222
+ GitHub API supports creating new repositories as well. To create a repository
223
+ we need to` POST ` some JSON containing the details and configuration options.
224
+
225
+ curl -i -H 'Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4' \
226
+ -d '{ \
227
+ "name": "blog", \
228
+ "auto_init": true, \
229
+ "private": true, \
230
+ "gitignore_template": "nanoc" \
231
+ }' \
232
+ https://api.github.com/user/repos
233
+
234
+ In this minimal example, we create a new repository for our blog (to be served
235
+ on[ GitHub Pages] [ pages ] perhaps). Though the blog will be public, we've made
236
+ the repository private. In this single step, we'll also init it with
237
+ a README and a[ nanoc] [ nanoc ] -flavored[ .gitignore template] [ gitignore
238
+ templates] .
130
239
131
- ###Paginate
240
+ The resulting repository will be found at `https://github.com/ <your
241
+ usrname>/blog`. To create a repository under an organization for which you're
242
+ an owner, just change the API method from` /user/repos ` to `/orgs/{org
243
+ name}/repos`.
132
244
133
- ## Star a repo
245
+ Let's fetch our newly created repository:
134
246
135
- ##Follow a user
247
+ curl -i https://api.github.com/pengwynn/blog
248
+
249
+ HTTP/1.1 404 Not Found
250
+
251
+ {
252
+ "message": "Not Found"
253
+ }
254
+
255
+ Oh noes! Where did it go? Since we created the repository as_ private_ , we need
256
+ to authenticate in order to see it. If you're a grizzled HTTP user, you might
257
+ expect a` 403 ` instead. Since we don't want to leak information about private
258
+ repositories, the GitHub API returns a` 404 ` instead as if to say "we can
259
+ neither confirm nor deny the existence of this repository."
260
+
261
+ ##Issues
262
+
263
+ The UI for Issues on GitHub aims to provide 'just enough' workflow while
264
+ staying out of your way. With the GitHub[ Issues API] [ issues-api ] , you can pull
265
+ data out or create issues from other tools to create a workflow that works for
266
+ your team.
267
+
268
+ Just like github.com, the API provides a few methods to view issues for the
269
+ authenticated user. To see all your issues,` GET /issues ` :
270
+
271
+
272
+ curl -i -H 'Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4' \
273
+ https://api.github.com/issues
274
+
275
+ To get only the issues under one of your GitHub organizations, `GET
276
+ /orgs/{org}/issues`:
277
+
278
+
279
+ curl -i -H 'Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4' \
280
+ https://api.github.com/orgs/rails/issues
281
+
282
+ We can also get all the issues under a single repository:
283
+
284
+ curl -i https://api.github.com/repos/rails/rails/issues
285
+
286
+ ###Pagination
287
+
288
+ A project the size of Rails has thousands of issues. We'll need to paginate,
289
+ making multiple API calls to get the data. Let's repeat that last call, this
290
+ time taking note of the response headers:
291
+
292
+ curl -i https://api.github.com/repos/rails/rails/issues
293
+
294
+ HTTP/1.1 200 OK
295
+
296
+ Link: <https://api.github.com/repos/rails/rails/issues?page=2>; rel="next",
297
+ <https://api.github.com/repos/rails/rails/issues?page=14>; rel="last"
298
+
299
+ The[ ` Link ` header] [ link-header ] provides a way for a response to link to
300
+ external resources, in this case additional pages of data. Since our call found
301
+ more than thirty issues (the default page size), the API tells us where we can
302
+ find the next page and the last page of results.
303
+
304
+ ###Creating an issue
305
+
306
+ Now that we've seen how to paginate lsits of issues, let's create an issue from
307
+ the API.
308
+
309
+ curl -i -H 'Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4' \
310
+ -d '{ \
311
+ "title": "New logo", \
312
+ "body": "We should have one", \
313
+ "labels": ["design"] \
314
+ }' \
315
+ https://api.github.com/repos/pengwynn/api-sandbox/issues
316
+
317
+ To create an issue, we need to be authenticated so we pass an
318
+ OAuth token in the header. We pass the title, body, and labels in the JSON
319
+ body to the` /issues ` path underneath the repository in which we want to create
320
+ the issue.
321
+
322
+ HTTP/1.1 201 Created
323
+ Location: https://api.github.com/repos/pengwynn/api-sandbox/issues/17
324
+ X-RateLimit-Limit: 5000
325
+
326
+ {
327
+ "pull_request": {
328
+ "patch_url": null,
329
+ "html_url": null,
330
+ "diff_url": null
331
+ },
332
+ "created_at": "2012-11-14T15:25:33Z",
333
+ "comments": 0,
334
+ "milestone": null,
335
+ "title": "New logo",
336
+ "body": "We should have one",
337
+ "user": {
338
+ "login": "pengwynn",
339
+ "gravatar_id": "7e19cd5486b5d6dc1ef90e671ba52ae0",
340
+ "avatar_url": "https://secure.gravatar.com/avatar/7e19cd5486b5d6dc1ef90e671ba52ae0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png",
341
+ "id": 865,
342
+ "url": "https://api.github.com/users/pengwynn"
343
+ },
344
+ "closed_at": null,
345
+ "updated_at": "2012-11-14T15:25:33Z",
346
+ "number": 17,
347
+ "closed_by": null,
348
+ "html_url": "https://github.com/pengwynn/api-sandbox/issues/17",
349
+ "labels": [
350
+ {
351
+ "color": "ededed",
352
+ "name": "design",
353
+ "url": "https://api.github.com/repos/pengwynn/api-sandbox/labels/design"
354
+ }
355
+ ],
356
+ "id": 8356941,
357
+ "assignee": null,
358
+ "state": "open",
359
+ "url": "https://api.github.com/repos/pengwynn/api-sandbox/issues/17"
360
+ }
361
+
362
+ The response gives us a couple of pointers to the newly created issue, both in
363
+ the` Location ` response header and the` url ` field of the JSON response.
364
+
365
+ ###Convert an issue to a Pull Request
366
+
367
+ GitHub moves fast and the API tries to keep pace, but there are some things you
368
+ can do with the API that you can't do on github.com. Using the API, you can
369
+ turn an issue into a Pull Request. Let's convert the issue we just created
370
+
371
+
372
+ curl -i -H 'Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4' \
373
+ -d '{ \
374
+ "issue": 17, \
375
+ "head": "new-feature", \
376
+ "base": "master" \
377
+ }' \
378
+ https://api.github.com/repos/pengwynn/api-sandbox/pulls
379
+
380
+ Using this shorthand, the details for the pull request title and body are taken
381
+ from the issue we provide.
136
382
137
383
##Conditional requests
138
384
139
- ##JSON-P
385
+ A big part of being a good API citizen is respecting rate limits and caching
386
+ information that does not change. The API supports[ conditional
387
+ requests] [ conditional-requests ] and helps you do the right thing. Consider the
388
+ first call we made to get@defunkt 's profile:
389
+
390
+ curl -i https://api.github.com/users/defunkt
391
+
392
+ In addition to the JSON body, take note of the HTTP status code of` 200 ` and
393
+ the` ETag ` header:
394
+
395
+ HTTP/1.1 200 OK
396
+ ETag: "bfd85cbf23ac0b0c8a29bee02e7117c6"
397
+
398
+ The ETag is a fingerprint of the response. If we pass that on subsequent calls,
399
+ we can tell the API to give us the resource again, only if it has changed:
400
+
401
+ curl -i -H 'If-None-Match: "644b5b0155e6404a9cc4bd9d8b1ae730"' \
402
+ https://api.github.com/users/defunkt
403
+
404
+ HTTP/1.1 304 OK
405
+
406
+ The` 304 ` status indicates that the resource hasn't changed since the last time
407
+ we asked for it and the response will contain no body. As a bonus,` 304 `
408
+ responses don't count against your[ rate limit] [ rate-limiting ] .
140
409
141
410
142
411
@@ -147,3 +416,11 @@ docs][scopes] for a full list of scopes.
147
416
[ webflow ] :http://developer.github.com/v3/oauth/#web-application-flow
148
417
[ authorizations api ] :http://developer.github.com/v3/oauth/#oauth-authorizations-api
149
418
[ scopes ] :http://developer.github.com/v3/oauth/#scopes
419
+ [ repos-api ] :http://developer.github.com/v3/repos/
420
+ [ pages ] :http://pages.github.com
421
+ [ nanoc ] :http://nanoc.stoneship.org/
422
+ [ gitignore templates ] :https://github.com/github/gitignore
423
+ [ issues-api ] :http://developer.github.com/v3/issues/
424
+ [ link-header ] :http://www.w3.org/wiki/LinkHeader
425
+ [ conditional-requests ] :http://developer.github.com/v3/#conditional-requests
426
+ [ rate-limiting ] :http://developer.github.com/v3/#rate-limiting