uwaterlooapi 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df7fff82780e9813ef8f71c739379e4399940e6b
4
- data.tar.gz: e2737a2a37d1980ce277f411bd91fcb0f8e7b43b
3
+ metadata.gz: a1e537c972acbbcfbaa9bbb5f36713afdf7ca894
4
+ data.tar.gz: c067bd94f83a50072121dd5ec40efd7dc6428f66
5
5
  SHA512:
6
- metadata.gz: d25b90c8f3963a44ec762ad3ce9bdfb6f7a7bb47e46d0d4ab583b0b86cf777ea88389af059d280ed5c257f42eaaa8ff42834f71c38d59d4f75988f2d18ba5383
7
- data.tar.gz: 8c0ae1334e60d337eb3dd4f8bdfccace57659d5e2521631fa11352cd57aacfae3927a56726e0459aa429224841be6578ccab96a1ceeb9a4ef90c32267176a5e3
6
+ metadata.gz: 07a9b653511e75cc05e18555f78e10230f11ac201dcb76902c627ffceea90d9d1abc454cb46a1b5fe133e61f9b4489c9ccfd7481b71b86eced607644a8e49f97
7
+ data.tar.gz: 9296049f687f1e21c8b3e166084fe00470eacf230baaa4f4b20117d7abc1f4b6ac56823dbfd0833be07ef0a5819dc832a6b6eaa364da4c70c395bedd4365ed8b
data/README.md CHANGED
@@ -12,7 +12,7 @@ RubyGem wrapper for the University of Waterloo OpenData API
12
12
  Add this line to your application's Gemfile:
13
13
 
14
14
  ```ruby
15
- gem 'middleman-protect-emails'
15
+ gem 'uwaterlooapi'
16
16
  ```
17
17
 
18
18
  And then run:
@@ -60,6 +60,6 @@ class UWaterlooAPI
60
60
  route '/api/changelog'
61
61
  route '/server/time'
62
62
  route '/server/codes'
63
-
63
+ route '/parking/watpark'
64
64
  end
65
65
  end
@@ -1,3 +1,3 @@
1
1
  class UWaterlooAPI
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
@@ -8,26 +8,17 @@ require 'uwaterlooapi'
8
8
  WebMock.disable_net_connect! allow_localhost: true, allow: 'codeclimate.com'
9
9
  Dir.chdir File.dirname __FILE__
10
10
 
11
- # This file was generated by the `rspec --init` command. Conventionally, all
12
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
13
- # Require this file using `require "spec_helper"` to ensure that it is only
14
- # loaded once.
15
- #
16
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
11
  RSpec.configure do |config|
18
12
  config.run_all_when_everything_filtered = true
19
- config.filter_run :focus
20
13
 
21
14
  config.before(:each) do
22
15
  stub_request(:get, 'https://api.uwaterloo.ca/v2/server/time.json?format=json&key=testkey').
23
16
  to_return(status: 200, body: File.new('responses/timestamp.json'), headers: { 'Content-Type' => 'application/json; charset=utf-8' })
24
17
  stub_request(:get, 'https://api.uwaterloo.ca/v2/terms/1139/CS/115/schedule.json?format=json&key=testkey').
25
18
  to_return(status: 200, body: File.new('responses/courses.json'), headers: { 'Content-Type' => 'application/json; charset=utf-8' })
19
+ stub_request(:get, 'https://api.uwaterloo.ca/v2/weather/current.json?format=json&key=testkey').
20
+ to_return(status: 500)
26
21
  end
27
22
 
28
- # Run specs in random order to surface order dependencies. If you find an
29
- # order dependency and want to debug it, you can fix the order by providing
30
- # the seed, which is printed after each run.
31
- # --seed 1234
32
- #config.order = 'random'
23
+ config.order = 'random'
33
24
  end
@@ -1,133 +1,305 @@
1
1
  require 'spec_helper'
2
2
  describe UWaterlooAPI do
3
- before :each do
4
- @api = UWaterlooAPI.new 'testkey'
5
- end
3
+ let(:api) { UWaterlooAPI.new 'testkey' }
6
4
 
7
5
  describe '#new' do
6
+
8
7
  it 'returns a new UWaterlooAPI object' do
9
- expect(@api).to be_an_instance_of(UWaterlooAPI)
8
+ expect(api).to be_an_instance_of(UWaterlooAPI)
10
9
  end
11
10
 
12
11
  it 'takes one parameter' do
13
12
  expect{ UWaterlooAPI.new }.to raise_exception ArgumentError
14
13
  end
15
14
 
16
- it 'returns a UWaterlooAPI::Query object for foodservices' do
17
- expect(@api.foodservices).to be_an_instance_of UWaterlooAPI::Query
18
- end
19
-
20
- it 'returns a UWaterlooAPI::Query object for courses' do
21
- expect(@api.courses).to be_an_instance_of UWaterlooAPI::Query
22
- end
23
-
24
- it 'returns a UWaterlooAPI::Query object for events' do
25
- expect(@api.events).to be_an_instance_of UWaterlooAPI::Query
26
- end
27
-
28
- it 'returns a UWaterlooAPI::Query object for news' do
29
- expect(@api.news).to be_an_instance_of UWaterlooAPI::Query
30
- end
31
-
32
- it 'returns a UWaterlooAPI::Query object for weather' do
33
- expect(@api.weather).to be_an_instance_of UWaterlooAPI::Query
34
- end
35
-
36
- it 'returns a UWaterlooAPI::Query object for terms' do
37
- expect(@api.terms).to be_an_instance_of UWaterlooAPI::Query
38
- end
39
-
40
- it 'returns a UWaterlooAPI::Query object for resources' do
41
- expect(@api.resources).to be_an_instance_of UWaterlooAPI::Query
42
- end
43
-
44
- it 'returns a UWaterlooAPI::Query object for codes' do
45
- expect(@api.codes).to be_an_instance_of UWaterlooAPI::Query
46
- end
47
-
48
- it 'returns a UWaterlooAPI::Query object for buildings' do
49
- expect(@api.buildings).to be_an_instance_of UWaterlooAPI::Query
50
- end
51
-
52
- it 'returns a UWaterlooAPI::Query object for api' do
53
- expect(@api.api).to be_an_instance_of UWaterlooAPI::Query
54
- end
55
-
56
- it 'returns a UWaterlooAPI::Query object for server' do
57
- expect(@api.server).to be_an_instance_of UWaterlooAPI::Query
58
- end
59
-
60
15
  context 'queries' do
61
- before :each do
62
- @time = @api.server.time
63
- end
16
+
17
+ let(:time) { api.server.time }
64
18
 
65
19
  it 'makes one request' do
66
- expect(@time.timestamp).to eq 1414369440
67
- expect(@time.datetime).to eq '2014-10-26T20:24:00-04:00'
20
+ expect(time.timestamp).to eq 1414369440
21
+ expect(time.datetime).to eq '2014-10-26T20:24:00-04:00'
68
22
  expect(a_request(:get, 'https://api.uwaterloo.ca/v2/server/time.json?format=json&key=testkey')).to have_been_made.once
69
23
  end
70
24
 
71
25
  it 'returns a RecursiveOpenStruct after response is parsed' do
72
- expect(@time.key_reset_time).to eq 1416978000
73
- expect(@time.get).to be_an_instance_of RecursiveOpenStruct
74
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule[0]).to be_an_instance_of RecursiveOpenStruct
26
+ expect(time.key_reset_time).to eq 1416978000
27
+ expect(time.get).to be_an_instance_of RecursiveOpenStruct
28
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule[0]).to be_an_instance_of RecursiveOpenStruct
75
29
  end
76
30
 
77
31
  it 'incrementally builds a query' do
78
- expect(@api.terms.term(1139)).to be_an_instance_of UWaterlooAPI::Query
79
- expect(@api.terms.term(1139).subject('CS')).to be_an_instance_of UWaterlooAPI::Query
80
- expect(@api.terms.term(1139).subject('CS').catalog_number(115)).to be_an_instance_of UWaterlooAPI::Query
81
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule).to be_an_instance_of UWaterlooAPI::Query
32
+ expect(api.terms).to be_an_instance_of UWaterlooAPI::Query
33
+ expect(api.terms.term(1139)).to be_an_instance_of UWaterlooAPI::Query
34
+ expect(api.terms.term(1139).subject('CS')).to be_an_instance_of UWaterlooAPI::Query
35
+ expect(api.terms.term(1139).subject('CS').catalog_number(115)).to be_an_instance_of UWaterlooAPI::Query
36
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule).to be_an_instance_of UWaterlooAPI::Query
82
37
  end
83
38
 
84
39
  it 'makes one request over an iteration' do
85
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule.map { |s| s.topic }).to be_an_instance_of Array
40
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule.map { |s| s.topic }).to be_an_instance_of Array
86
41
  expect(a_request(:get, 'https://api.uwaterloo.ca/v2/terms/1139/CS/115/schedule.json?format=json&key=testkey')).to have_been_made.once
87
42
  end
88
43
 
89
44
  it 'can directly retrieve a deeply nested property' do
90
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule[1].classes[0].instructors[0]).to eq 'Vasiga,Troy Michael John'
91
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule[5].classes[0].date.end_time).to eq '11:20'
92
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule[8].reserves.empty?).to eq true
93
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule[14].class_number).to eq 5951
94
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule[18].topic.nil?).to eq true
95
- expect(@api.terms.term(1139).subject('CS').catalog_number(115).schedule[19].classes[0].date.is_closed).to eq false
45
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule[1].classes[0].instructors[0]).to eq 'Vasiga,Troy Michael John'
46
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule[5].classes[0].date.end_time).to eq '11:20'
47
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule[8].reserves.empty?).to eq true
48
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule[14].class_number).to eq 5951
49
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule[18].topic.nil?).to eq true
50
+ expect(api.terms.term(1139).subject('CS').catalog_number(115).schedule[19].classes[0].date.is_closed).to eq false
96
51
  expect(a_request(:get, 'https://api.uwaterloo.ca/v2/terms/1139/CS/115/schedule.json?format=json&key=testkey')).to have_been_made.times 6
97
52
  end
98
53
 
99
54
  it 'makes one request per get call' do
100
- expect(@time.get).to be_an_instance_of RecursiveOpenStruct
101
- expect(@time.get).to be_an_instance_of RecursiveOpenStruct
55
+ expect(time.get).to be_an_instance_of RecursiveOpenStruct
56
+ expect(time.get).to be_an_instance_of RecursiveOpenStruct
102
57
  expect(a_request(:get, 'https://api.uwaterloo.ca/v2/server/time.json?format=json&key=testkey')).to have_been_made.times 2
103
58
  end
104
59
 
105
60
  it 'has a working respond_to? method' do
106
- expect(@time.respond_to?(:get)).to eq true
107
- expect(@time.respond_to?(:timestamp)).to eq true
108
- expect(@time.respond_to?(:lolhi)).to eq false
61
+ expect(time.respond_to?(:get)).to eq true
62
+ expect(time.respond_to?(:timestamp)).to eq true
63
+ expect(time.respond_to?(:lolhi)).to eq false
109
64
  expect(a_request(:get, 'https://api.uwaterloo.ca/v2/server/time.json?format=json&key=testkey')).to have_been_made.once
110
65
  end
111
66
 
112
67
  it 'has a working method_missing method' do
113
- expect{ @time.hellobrah }.to raise_exception NoMethodError
114
- expect(@time.timezone).to eq 'EDT'
68
+ expect{ time.hellobrah }.to raise_exception NoMethodError
69
+ expect(time.timezone).to eq 'EDT'
115
70
  expect(a_request(:get, 'https://api.uwaterloo.ca/v2/server/time.json?format=json&key=testkey')).to have_been_made.once
116
71
  end
117
72
 
118
73
  it 'allows retrieval of metadata' do
119
- expect{ @time.meta(:status) }.to raise_exception RuntimeError
120
- @time.get
121
- expect(@time.meta(:status)).to eq 200
122
- expect(@time.meta(:method_id)).to eq 1087
74
+ expect{ time.meta(:status) }.to raise_exception RuntimeError
75
+ time.get
76
+ expect(time.meta(:status)).to eq 200
77
+ expect(time.meta(:method_id)).to eq 1087
123
78
  end
124
79
 
125
80
  it 'can detect invalid method chains' do
126
- expect{ @api.terms.get }.to raise_exception NoMethodError
127
- expect{ @api.terms.term(1139).meta :status }.to raise_exception NoMethodError
128
- expect{ @api.courses.catalog_number(1) }.to raise_exception NoMethodError
129
- expect{ @api.foodservices.year(2013).announcements }.to raise_exception NoMethodError
81
+ expect{ api.terms.get }.to raise_exception NoMethodError
82
+ expect{ api.terms.term(1139).meta :status }.to raise_exception NoMethodError
83
+ expect{ api.courses.catalog_number(1) }.to raise_exception NoMethodError
84
+ expect{ api.foodservices.year(2013).announcements }.to raise_exception NoMethodError
85
+ end
86
+
87
+ it 'returns an error when the server has an error' do
88
+ expect{ api.weather.current.get }.to raise_exception
89
+ end
90
+
91
+ end
92
+
93
+ context 'list of valid routes' do
94
+
95
+ it 'includes foodservices/menu' do
96
+ expect(api.foodservices.menu).to be_an_instance_of UWaterlooAPI::Query
97
+ end
98
+
99
+ it 'includes foodservices/menu' do
100
+ expect(api.foodservices.menu).to be_an_instance_of UWaterlooAPI::Query
101
+ end
102
+
103
+ it 'includes foodservices/notes' do
104
+ expect(api.foodservices.notes).to be_an_instance_of UWaterlooAPI::Query
105
+ end
106
+
107
+ it 'includes foodservices/diets' do
108
+ expect(api.foodservices.diets).to be_an_instance_of UWaterlooAPI::Query
109
+ end
110
+
111
+ it 'includes foodservices/outlets' do
112
+ expect(api.foodservices.outlets).to be_an_instance_of UWaterlooAPI::Query
113
+ end
114
+
115
+ it 'includes foodservices/locations' do
116
+ expect(api.foodservices.locations).to be_an_instance_of UWaterlooAPI::Query
117
+ end
118
+
119
+ it 'includes foodservices/watcard' do
120
+ expect(api.foodservices.watcard).to be_an_instance_of UWaterlooAPI::Query
121
+ end
122
+
123
+ it 'includes foodservices/announcements' do
124
+ expect(api.foodservices.announcements).to be_an_instance_of UWaterlooAPI::Query
125
+ end
126
+
127
+ it 'includes foodservices/products/{product_id}' do
128
+ expect(api.foodservices.products.product_id(1386)).to be_an_instance_of UWaterlooAPI::Query
129
+ end
130
+
131
+ it 'includes foodservices/{year}/{week}/menu' do
132
+ expect(api.foodservices.year(2014).week(2).menu).to be_an_instance_of UWaterlooAPI::Query
133
+ end
134
+
135
+ it 'includes foodservices/{year}/{week}/notes' do
136
+ expect(api.foodservices.year(2014).week(2).notes).to be_an_instance_of UWaterlooAPI::Query
137
+ end
138
+
139
+ it 'includes foodservices/{year}/{week}/announcements' do
140
+ expect(api.foodservices.year(2014).week(2).announcements).to be_an_instance_of UWaterlooAPI::Query
141
+ end
142
+
143
+ it 'includes courses/{subject}' do
144
+ expect(api.courses.subject('MATH')).to be_an_instance_of UWaterlooAPI::Query
145
+ end
146
+
147
+ it 'includes courses/{course_id}' do
148
+ expect(api.courses.course_id(7407)).to be_an_instance_of UWaterlooAPI::Query
149
+ end
150
+
151
+ it 'includes courses/{class_number}/schedule' do
152
+ expect(api.courses.class_number(5377).schedule).to be_an_instance_of UWaterlooAPI::Query
153
+ end
154
+
155
+ it 'includes courses/{subject}/{catalog_number}' do
156
+ expect(api.courses.subject('MATH').catalog_number(115)).to be_an_instance_of UWaterlooAPI::Query
157
+ end
158
+
159
+ it 'includes courses/{subject}/{catalog_number}/schedule' do
160
+ expect(api.courses.subject('MATH').catalog_number(115).schedule).to be_an_instance_of UWaterlooAPI::Query
161
+ end
162
+
163
+ it 'includes courses/{subject}/{catalog_number}/prerequisites' do
164
+ expect(api.courses.subject('MATH').catalog_number(115).prerequisites).to be_an_instance_of UWaterlooAPI::Query
165
+ end
166
+
167
+ it 'includes courses/{subject}/{catalog_number}/examschedule' do
168
+ expect(api.courses.subject('MATH').catalog_number(115).examschedule).to be_an_instance_of UWaterlooAPI::Query
169
+ end
170
+
171
+ it 'includes events' do
172
+ expect(api.events).to be_an_instance_of UWaterlooAPI::Query
173
+ end
174
+
175
+ it 'includes events/{site}' do
176
+ expect(api.events.site('engineering')).to be_an_instance_of UWaterlooAPI::Query
177
+ end
178
+
179
+ it 'includes events/{site}/{id}' do
180
+ expect(api.events.site('engineering').id(1701)).to be_an_instance_of UWaterlooAPI::Query
181
+ end
182
+
183
+ it 'includes events/holidays' do
184
+ expect(api.events.holidays).to be_an_instance_of UWaterlooAPI::Query
185
+ end
186
+
187
+ it 'includes news' do
188
+ expect(api.news).to be_an_instance_of UWaterlooAPI::Query
189
+ end
190
+
191
+ it 'includes news/{site}' do
192
+ expect(api.news.site('engineering')).to be_an_instance_of UWaterlooAPI::Query
193
+ end
194
+
195
+ it 'includes news/{site}/{id}' do
196
+ expect(api.news.site('engineering').id(881)).to be_an_instance_of UWaterlooAPI::Query
197
+ end
198
+
199
+ it 'includes weather/current' do
200
+ expect(api.weather.current).to be_an_instance_of UWaterlooAPI::Query
201
+ end
202
+
203
+ it 'includes terms/list' do
204
+ expect(api.terms.list).to be_an_instance_of UWaterlooAPI::Query
205
+ end
206
+
207
+ it 'includes terms/{term}/examschedule' do
208
+ expect(api.terms.term(1139).examschedule).to be_an_instance_of UWaterlooAPI::Query
209
+ end
210
+
211
+ it 'includes terms/{term}/{subject}/schedule' do
212
+ expect(api.terms.term(1139).subject('MATH').schedule).to be_an_instance_of UWaterlooAPI::Query
213
+ end
214
+
215
+ it 'includes terms/{term}/{subject}/{catalog_number}/schedule' do
216
+ expect(api.terms.term(1139).subject('MATH').catalog_number(115).schedule).to be_an_instance_of UWaterlooAPI::Query
217
+ end
218
+
219
+ it 'includes terms/{term}/infosessions' do
220
+ expect(api.terms.term(1139).infosessions).to be_an_instance_of UWaterlooAPI::Query
221
+ end
222
+
223
+ it 'includes resources/tutors' do
224
+ expect(api.resources.tutors).to be_an_instance_of UWaterlooAPI::Query
225
+ end
226
+
227
+ it 'includes resources/printers' do
228
+ expect(api.resources.printers).to be_an_instance_of UWaterlooAPI::Query
229
+ end
230
+
231
+ it 'includes resources/infosessions' do
232
+ expect(api.resources.infosessions).to be_an_instance_of UWaterlooAPI::Query
233
+ end
234
+
235
+ it 'includes resources/goosewatch' do
236
+ expect(api.resources.goosewatch).to be_an_instance_of UWaterlooAPI::Query
237
+ end
238
+
239
+ it 'includes codes/units' do
240
+ expect(api.codes.units).to be_an_instance_of UWaterlooAPI::Query
241
+ end
242
+
243
+ it 'includes codes/terms' do
244
+ expect(api.codes.terms).to be_an_instance_of UWaterlooAPI::Query
245
+ end
246
+
247
+ it 'includes codes/groups' do
248
+ expect(api.codes.groups).to be_an_instance_of UWaterlooAPI::Query
249
+ end
250
+
251
+ it 'includes codes/subjects' do
252
+ expect(api.codes.subjects).to be_an_instance_of UWaterlooAPI::Query
253
+ end
254
+
255
+ it 'includes codes/instructions' do
256
+ expect(api.codes.instructions).to be_an_instance_of UWaterlooAPI::Query
257
+ end
258
+
259
+ it 'includes buildings/list' do
260
+ expect(api.buildings.list).to be_an_instance_of UWaterlooAPI::Query
261
+ end
262
+
263
+ it 'includes buildings/{building_code}' do
264
+ expect(api.buildings.building_code('MHR')).to be_an_instance_of UWaterlooAPI::Query
265
+ end
266
+
267
+ it 'includes buildings/{building}/{room}/courses' do
268
+ expect(api.buildings.building('MC').room(2038).courses).to be_an_instance_of UWaterlooAPI::Query
130
269
  end
270
+
271
+ it 'includes api/usage' do
272
+ expect(api.api.usage).to be_an_instance_of UWaterlooAPI::Query
273
+ end
274
+
275
+ it 'includes api/services' do
276
+ expect(api.api.services).to be_an_instance_of UWaterlooAPI::Query
277
+ end
278
+
279
+ it 'includes api/methods' do
280
+ expect(api.api.methods).to be_an_instance_of UWaterlooAPI::Query
281
+ end
282
+
283
+ it 'includes api/versions' do
284
+ expect(api.api.versions).to be_an_instance_of UWaterlooAPI::Query
285
+ end
286
+
287
+ it 'includes api/changelog' do
288
+ expect(api.api.changelog).to be_an_instance_of UWaterlooAPI::Query
289
+ end
290
+
291
+ it 'includes server/time' do
292
+ expect(api.server.time).to be_an_instance_of UWaterlooAPI::Query
293
+ end
294
+
295
+ it 'includes server/codes' do
296
+ expect(api.server.codes).to be_an_instance_of UWaterlooAPI::Query
297
+ end
298
+
299
+ it 'includes parking/watpark' do
300
+ expect(api.parking.watpark).to be_an_instance_of UWaterlooAPI::Query
301
+ end
302
+
131
303
  end
132
304
  end
133
305
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uwaterlooapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ankit Sardesai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-25 00:00:00.000000000 Z
11
+ date: 2015-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -108,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
108
  version: '0'
109
109
  requirements: []
110
110
  rubyforge_project:
111
- rubygems_version: 2.4.5
111
+ rubygems_version: 2.4.3
112
112
  signing_key:
113
113
  specification_version: 4
114
114
  summary: Ruby Gem wrapper for the University of Waterloo Open Data API