claude-agent-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -0
- package/bin/claude-framework +3 -0
- package/framework/agents/design-lead.md +240 -0
- package/framework/agents/product-owner.md +179 -0
- package/framework/agents/tech-lead.md +226 -0
- package/framework/commands/ayuda.md +127 -0
- package/framework/commands/a/303/261adir.md +98 -0
- package/framework/commands/backup.md +397 -0
- package/framework/commands/cambiar.md +110 -0
- package/framework/commands/cloud.md +457 -0
- package/framework/commands/code.md +142 -0
- package/framework/commands/debug.md +334 -0
- package/framework/commands/deploy.md +383 -0
- package/framework/commands/deshacer.md +120 -0
- package/framework/commands/estado.md +218 -0
- package/framework/commands/explica.md +227 -0
- package/framework/commands/feature.md +120 -0
- package/framework/commands/git.md +427 -0
- package/framework/commands/historial.md +202 -0
- package/framework/commands/learn.md +408 -0
- package/framework/commands/movil.md +245 -0
- package/framework/commands/nuevo.md +118 -0
- package/framework/commands/plan.md +134 -0
- package/framework/commands/prd.md +113 -0
- package/framework/commands/probar.md +148 -0
- package/framework/commands/revisar.md +208 -0
- package/framework/commands/seeds.md +230 -0
- package/framework/commands/seguridad.md +226 -0
- package/framework/commands/tasks.md +157 -0
- package/framework/skills/architecture/algorithms.md +970 -0
- package/framework/skills/architecture/clean-code.md +1080 -0
- package/framework/skills/architecture/design-patterns.md +1984 -0
- package/framework/skills/architecture/functional-programming.md +972 -0
- package/framework/skills/architecture/solid.md +991 -0
- package/framework/skills/cloud/cloud-aws.md +848 -0
- package/framework/skills/cloud/cloud-azure.md +931 -0
- package/framework/skills/cloud/cloud-gcp.md +848 -0
- package/framework/skills/cloud/message-queues.md +1229 -0
- package/framework/skills/core/accessibility.md +401 -0
- package/framework/skills/core/api.md +474 -0
- package/framework/skills/core/authentication.md +306 -0
- package/framework/skills/core/authorization.md +388 -0
- package/framework/skills/core/background-jobs.md +341 -0
- package/framework/skills/core/caching.md +473 -0
- package/framework/skills/core/code-review.md +341 -0
- package/framework/skills/core/controllers.md +290 -0
- package/framework/skills/core/cua.md +285 -0
- package/framework/skills/core/documentation.md +472 -0
- package/framework/skills/core/file-uploads.md +351 -0
- package/framework/skills/core/hotwire-native.md +296 -0
- package/framework/skills/core/hotwire.md +278 -0
- package/framework/skills/core/i18n.md +334 -0
- package/framework/skills/core/imports-exports.md +750 -0
- package/framework/skills/core/infrastructure.md +337 -0
- package/framework/skills/core/models.md +228 -0
- package/framework/skills/core/notifications.md +672 -0
- package/framework/skills/core/payments.md +581 -0
- package/framework/skills/core/performance.md +361 -0
- package/framework/skills/core/rails-scaffold.md +131 -0
- package/framework/skills/core/search.md +518 -0
- package/framework/skills/core/security.md +565 -0
- package/framework/skills/core/seeds.md +307 -0
- package/framework/skills/core/seo.md +542 -0
- package/framework/skills/core/testing.md +393 -0
- package/framework/skills/core/views.md +260 -0
- package/framework/skills/core/websockets.md +564 -0
- package/framework/skills/data/advanced-sql.md +1204 -0
- package/framework/skills/data/nosql.md +1141 -0
- package/framework/skills/devops/containers-advanced.md +1237 -0
- package/framework/skills/devops/debugging.md +834 -0
- package/framework/skills/devops/git-workflow.md +752 -0
- package/framework/skills/devops/networking.md +932 -0
- package/framework/skills/devops/shell-scripting.md +1132 -0
- package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
- package/framework/sub-agents/cloud-agent.md +677 -0
- package/framework/sub-agents/data.md +504 -0
- package/framework/sub-agents/debugging-agent.md +554 -0
- package/framework/sub-agents/devops.md +483 -0
- package/framework/sub-agents/docs.md +176 -0
- package/framework/sub-agents/frontend-dev.md +349 -0
- package/framework/sub-agents/git-workflow-agent.md +697 -0
- package/framework/sub-agents/integrations.md +630 -0
- package/framework/sub-agents/native-dev.md +434 -0
- package/framework/sub-agents/qa.md +138 -0
- package/framework/sub-agents/rails-dev.md +375 -0
- package/framework/sub-agents/security.md +526 -0
- package/framework/sub-agents/ui.md +437 -0
- package/framework/sub-agents/ux.md +284 -0
- package/framework/templates/api-spec.md +500 -0
- package/framework/templates/component-spec.md +248 -0
- package/framework/templates/feature.json +13 -0
- package/framework/templates/model-spec.md +318 -0
- package/framework/templates/prd-template.md +80 -0
- package/framework/templates/task-plan.md +122 -0
- package/framework/templates/task-user-story.md +52 -0
- package/framework/templates/technical-spec.md +260 -0
- package/framework/templates/user-story.md +95 -0
- package/package.json +42 -0
- package/project-templates/CLAUDE.md +42 -0
- package/project-templates/contexts/architecture.md +25 -0
- package/project-templates/contexts/conventions.md +46 -0
- package/project-templates/contexts/design-system.md +47 -0
- package/project-templates/contexts/requirements.md +38 -0
- package/project-templates/contexts/stack.md +30 -0
- package/project-templates/history/active/models.md +11 -0
- package/project-templates/history/changelog.md +15 -0
- package/project-templates/workspace/.gitkeep +0 -0
- package/src/cli.js +52 -0
- package/src/init.js +104 -0
- package/src/status.js +75 -0
- package/src/update.js +88 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# Skill: API
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Build RESTful JSON APIs for external integrations and mobile apps.
|
|
5
|
+
|
|
6
|
+
## API Controller Setup
|
|
7
|
+
|
|
8
|
+
### Base Controller
|
|
9
|
+
```ruby
|
|
10
|
+
# app/controllers/api/v1/base_controller.rb
|
|
11
|
+
module Api
|
|
12
|
+
module V1
|
|
13
|
+
class BaseController < ApplicationController
|
|
14
|
+
skip_before_action :verify_authenticity_token
|
|
15
|
+
before_action :authenticate_api_request
|
|
16
|
+
|
|
17
|
+
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
|
18
|
+
rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity
|
|
19
|
+
rescue_from ActionController::ParameterMissing, with: :bad_request
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def authenticate_api_request
|
|
24
|
+
token = request.headers["Authorization"]&.split(" ")&.last
|
|
25
|
+
@current_api_user = User.find_by(api_token: token)
|
|
26
|
+
|
|
27
|
+
render_unauthorized unless @current_api_user
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def current_api_user
|
|
31
|
+
@current_api_user
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def render_unauthorized
|
|
35
|
+
render json: { error: "Unauthorized" }, status: :unauthorized
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def not_found(exception)
|
|
39
|
+
render json: { error: exception.message }, status: :not_found
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def unprocessable_entity(exception)
|
|
43
|
+
render json: {
|
|
44
|
+
error: "Validation failed",
|
|
45
|
+
details: exception.record.errors.full_messages
|
|
46
|
+
}, status: :unprocessable_entity
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def bad_request(exception)
|
|
50
|
+
render json: { error: exception.message }, status: :bad_request
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Resource Controller
|
|
58
|
+
```ruby
|
|
59
|
+
# app/controllers/api/v1/articles_controller.rb
|
|
60
|
+
module Api
|
|
61
|
+
module V1
|
|
62
|
+
class ArticlesController < BaseController
|
|
63
|
+
before_action :set_article, only: [:show, :update, :destroy]
|
|
64
|
+
|
|
65
|
+
def index
|
|
66
|
+
@articles = Article.published
|
|
67
|
+
.includes(:user, :tags)
|
|
68
|
+
.order(created_at: :desc)
|
|
69
|
+
.page(params[:page])
|
|
70
|
+
.per(params[:per_page] || 20)
|
|
71
|
+
|
|
72
|
+
render json: {
|
|
73
|
+
articles: @articles.map { |a| article_json(a) },
|
|
74
|
+
meta: pagination_meta(@articles)
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def show
|
|
79
|
+
render json: { article: article_json(@article, full: true) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create
|
|
83
|
+
@article = current_api_user.articles.create!(article_params)
|
|
84
|
+
render json: { article: article_json(@article) }, status: :created
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def update
|
|
88
|
+
authorize_owner!(@article)
|
|
89
|
+
@article.update!(article_params)
|
|
90
|
+
render json: { article: article_json(@article) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def destroy
|
|
94
|
+
authorize_owner!(@article)
|
|
95
|
+
@article.destroy
|
|
96
|
+
head :no_content
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def set_article
|
|
102
|
+
@article = Article.find(params[:id])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def article_params
|
|
106
|
+
params.require(:article).permit(:title, :body, :published, tag_ids: [])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def authorize_owner!(resource)
|
|
110
|
+
unless resource.user == current_api_user
|
|
111
|
+
render json: { error: "Forbidden" }, status: :forbidden
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def article_json(article, full: false)
|
|
116
|
+
json = {
|
|
117
|
+
id: article.id,
|
|
118
|
+
title: article.title,
|
|
119
|
+
published: article.published,
|
|
120
|
+
created_at: article.created_at.iso8601,
|
|
121
|
+
author: {
|
|
122
|
+
id: article.user.id,
|
|
123
|
+
name: article.user.name
|
|
124
|
+
},
|
|
125
|
+
tags: article.tags.pluck(:name)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if full
|
|
129
|
+
json[:body] = article.body
|
|
130
|
+
json[:comments_count] = article.comments_count
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
json
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def pagination_meta(collection)
|
|
137
|
+
{
|
|
138
|
+
current_page: collection.current_page,
|
|
139
|
+
total_pages: collection.total_pages,
|
|
140
|
+
total_count: collection.total_count,
|
|
141
|
+
per_page: collection.limit_value
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Routes
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
# config/routes.rb
|
|
153
|
+
Rails.application.routes.draw do
|
|
154
|
+
namespace :api do
|
|
155
|
+
namespace :v1 do
|
|
156
|
+
resources :articles, only: [:index, :show, :create, :update, :destroy] do
|
|
157
|
+
resources :comments, only: [:index, :create, :destroy]
|
|
158
|
+
end
|
|
159
|
+
resources :users, only: [:show, :update]
|
|
160
|
+
resource :session, only: [:create, :destroy]
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Authentication
|
|
167
|
+
|
|
168
|
+
### Token-Based Auth
|
|
169
|
+
```ruby
|
|
170
|
+
# app/models/user.rb
|
|
171
|
+
class User < ApplicationRecord
|
|
172
|
+
has_secure_token :api_token
|
|
173
|
+
|
|
174
|
+
def regenerate_api_token!
|
|
175
|
+
regenerate_api_token
|
|
176
|
+
save!
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Migration
|
|
181
|
+
class AddApiTokenToUsers < ActiveRecord::Migration[8.0]
|
|
182
|
+
def change
|
|
183
|
+
add_column :users, :api_token, :string
|
|
184
|
+
add_index :users, :api_token, unique: true
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Session Controller
|
|
190
|
+
```ruby
|
|
191
|
+
# app/controllers/api/v1/sessions_controller.rb
|
|
192
|
+
module Api
|
|
193
|
+
module V1
|
|
194
|
+
class SessionsController < BaseController
|
|
195
|
+
skip_before_action :authenticate_api_request, only: [:create]
|
|
196
|
+
|
|
197
|
+
def create
|
|
198
|
+
user = User.authenticate_by(
|
|
199
|
+
email_address: params[:email],
|
|
200
|
+
password: params[:password]
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if user
|
|
204
|
+
user.regenerate_api_token! unless user.api_token
|
|
205
|
+
render json: {
|
|
206
|
+
user: user_json(user),
|
|
207
|
+
token: user.api_token
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
render json: { error: "Invalid credentials" }, status: :unauthorized
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def destroy
|
|
215
|
+
current_api_user.regenerate_api_token!
|
|
216
|
+
head :no_content
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def user_json(user)
|
|
222
|
+
{
|
|
223
|
+
id: user.id,
|
|
224
|
+
email: user.email_address,
|
|
225
|
+
name: user.name
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Request/Response Format
|
|
234
|
+
|
|
235
|
+
### Request Headers
|
|
236
|
+
```
|
|
237
|
+
Content-Type: application/json
|
|
238
|
+
Authorization: Bearer <token>
|
|
239
|
+
Accept: application/json
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Response Format
|
|
243
|
+
```json
|
|
244
|
+
// Success (single resource)
|
|
245
|
+
{
|
|
246
|
+
"article": {
|
|
247
|
+
"id": 1,
|
|
248
|
+
"title": "Hello World",
|
|
249
|
+
"body": "Content here..."
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Success (collection)
|
|
254
|
+
{
|
|
255
|
+
"articles": [...],
|
|
256
|
+
"meta": {
|
|
257
|
+
"current_page": 1,
|
|
258
|
+
"total_pages": 5,
|
|
259
|
+
"total_count": 100
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Error
|
|
264
|
+
{
|
|
265
|
+
"error": "Validation failed",
|
|
266
|
+
"details": [
|
|
267
|
+
"Title can't be blank",
|
|
268
|
+
"Body is too short"
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Serializers (Optional)
|
|
274
|
+
|
|
275
|
+
### Using ActiveModel Serializers
|
|
276
|
+
```ruby
|
|
277
|
+
# Gemfile
|
|
278
|
+
gem "active_model_serializers"
|
|
279
|
+
|
|
280
|
+
# app/serializers/article_serializer.rb
|
|
281
|
+
class ArticleSerializer < ActiveModel::Serializer
|
|
282
|
+
attributes :id, :title, :body, :published, :created_at
|
|
283
|
+
|
|
284
|
+
belongs_to :user
|
|
285
|
+
has_many :tags
|
|
286
|
+
|
|
287
|
+
def created_at
|
|
288
|
+
object.created_at.iso8601
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Controller
|
|
293
|
+
def show
|
|
294
|
+
render json: @article
|
|
295
|
+
end
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Using Blueprinter
|
|
299
|
+
```ruby
|
|
300
|
+
# Gemfile
|
|
301
|
+
gem "blueprinter"
|
|
302
|
+
|
|
303
|
+
# app/blueprints/article_blueprint.rb
|
|
304
|
+
class ArticleBlueprint < Blueprinter::Base
|
|
305
|
+
identifier :id
|
|
306
|
+
|
|
307
|
+
fields :title, :published
|
|
308
|
+
|
|
309
|
+
field :created_at do |article|
|
|
310
|
+
article.created_at.iso8601
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
association :user, blueprint: UserBlueprint
|
|
314
|
+
association :tags, blueprint: TagBlueprint
|
|
315
|
+
|
|
316
|
+
view :full do
|
|
317
|
+
field :body
|
|
318
|
+
field :comments_count
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Controller
|
|
323
|
+
def show
|
|
324
|
+
render json: ArticleBlueprint.render(@article, view: :full)
|
|
325
|
+
end
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Versioning
|
|
329
|
+
|
|
330
|
+
### URL Versioning
|
|
331
|
+
```ruby
|
|
332
|
+
# config/routes.rb
|
|
333
|
+
namespace :api do
|
|
334
|
+
namespace :v1 do
|
|
335
|
+
resources :articles
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
namespace :v2 do
|
|
339
|
+
resources :articles
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Header Versioning
|
|
345
|
+
```ruby
|
|
346
|
+
# app/controllers/api/base_controller.rb
|
|
347
|
+
class Api::BaseController < ApplicationController
|
|
348
|
+
before_action :set_api_version
|
|
349
|
+
|
|
350
|
+
private
|
|
351
|
+
|
|
352
|
+
def set_api_version
|
|
353
|
+
@api_version = request.headers["Api-Version"] || "v1"
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Rate Limiting
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
# app/controllers/api/v1/base_controller.rb
|
|
362
|
+
class Api::V1::BaseController < ApplicationController
|
|
363
|
+
include RateLimiting
|
|
364
|
+
|
|
365
|
+
rate_limit to: 100, within: 1.minute
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# app/controllers/concerns/rate_limiting.rb
|
|
369
|
+
module RateLimiting
|
|
370
|
+
extend ActiveSupport::Concern
|
|
371
|
+
|
|
372
|
+
class_methods do
|
|
373
|
+
def rate_limit(to:, within:)
|
|
374
|
+
before_action -> { check_rate_limit(to, within) }
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
private
|
|
379
|
+
|
|
380
|
+
def check_rate_limit(limit, period)
|
|
381
|
+
key = "rate_limit:#{current_api_user&.id || request.ip}"
|
|
382
|
+
count = Rails.cache.increment(key, 1, expires_in: period)
|
|
383
|
+
|
|
384
|
+
if count > limit
|
|
385
|
+
render json: {
|
|
386
|
+
error: "Rate limit exceeded",
|
|
387
|
+
retry_after: period.to_i
|
|
388
|
+
}, status: :too_many_requests
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Testing
|
|
395
|
+
|
|
396
|
+
```ruby
|
|
397
|
+
# spec/requests/api/v1/articles_spec.rb
|
|
398
|
+
require "rails_helper"
|
|
399
|
+
|
|
400
|
+
RSpec.describe "API V1 Articles", type: :request do
|
|
401
|
+
let(:user) { create(:user) }
|
|
402
|
+
let(:headers) do
|
|
403
|
+
{
|
|
404
|
+
"Authorization" => "Bearer #{user.api_token}",
|
|
405
|
+
"Content-Type" => "application/json"
|
|
406
|
+
}
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
describe "GET /api/v1/articles" do
|
|
410
|
+
let!(:articles) { create_list(:article, 3, :published) }
|
|
411
|
+
|
|
412
|
+
it "returns articles" do
|
|
413
|
+
get "/api/v1/articles", headers: headers
|
|
414
|
+
|
|
415
|
+
expect(response).to have_http_status(:ok)
|
|
416
|
+
expect(json_response["articles"].size).to eq(3)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
describe "POST /api/v1/articles" do
|
|
421
|
+
let(:valid_params) do
|
|
422
|
+
{ article: { title: "Test", body: "Content" } }
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
it "creates article" do
|
|
426
|
+
post "/api/v1/articles",
|
|
427
|
+
params: valid_params.to_json,
|
|
428
|
+
headers: headers
|
|
429
|
+
|
|
430
|
+
expect(response).to have_http_status(:created)
|
|
431
|
+
expect(json_response["article"]["title"]).to eq("Test")
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def json_response
|
|
436
|
+
JSON.parse(response.body)
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Documentation
|
|
442
|
+
|
|
443
|
+
### Request/Response Examples
|
|
444
|
+
```ruby
|
|
445
|
+
# doc/api/articles.md
|
|
446
|
+
## Articles API
|
|
447
|
+
|
|
448
|
+
### List Articles
|
|
449
|
+
GET /api/v1/articles
|
|
450
|
+
|
|
451
|
+
#### Parameters
|
|
452
|
+
| Name | Type | Description |
|
|
453
|
+
|------|------|-------------|
|
|
454
|
+
| page | integer | Page number |
|
|
455
|
+
| per_page | integer | Items per page (max 100) |
|
|
456
|
+
|
|
457
|
+
#### Response
|
|
458
|
+
```json
|
|
459
|
+
{
|
|
460
|
+
"articles": [...],
|
|
461
|
+
"meta": { "current_page": 1, "total_pages": 5 }
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Best Practices
|
|
467
|
+
|
|
468
|
+
1. **Version your API** - Support old clients during transitions
|
|
469
|
+
2. **Use proper HTTP status codes** - 200, 201, 204, 400, 401, 403, 404, 422, 429
|
|
470
|
+
3. **Paginate collections** - Don't return unbounded lists
|
|
471
|
+
4. **Include metadata** - Pagination info, timestamps
|
|
472
|
+
5. **Handle errors consistently** - Same format for all errors
|
|
473
|
+
6. **Rate limit** - Protect against abuse
|
|
474
|
+
7. **Document everything** - Request/response examples
|