Mobile api design
Skill almasumdev/awesome-mobile-backend-agent-skills/.github/skills/api/mobile-api-design
Design mobile-friendly HTTP APIs with predictable pagination, filtering, sorting, sparse/partial responses, and a consistent error envelope. Use when specifying new endpoints or reviewing existing ones for mobile use.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-backend-agent-skills --skill mobile-api-designAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.5 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
Mobile API Design
Instructions
Mobile clients run on flaky networks, long-lived installs, and limited CPU/battery. API shape decisions made on day one outlive any single app version. Design for the pessimistic case: 3G, stale app, cold cache.
1. Resource Shape
Prefer nouns and predictable plurals: /v1/articles, /v1/articles/{id}, /v1/articles/{id}/comments. Keep resource shapes consistent across endpoints -- the same Article object comes back from list, detail, and search.
Every resource has a stable id (ULID or UUIDv7 preferred; monotonic and sortable), created_at and updated_at in ISO 8601 UTC, and a version or etag for optimistic concurrency where applicable.
2. Pagination
Default to opaque cursor pagination. The cursor is a server-encoded token; clients must treat it as opaque bytes.
GET /v1/articles?limit=20&cursor=eyJpZCI6IjAxSFgifQ
{
"items": [ /* ... */ ],
"next_cursor": "eyJpZCI6IjAxSFkifQ",
"has_more": true
}
Implementation (Node/TS, Postgres):
// server
const limit = Math.min(Number(req.query.limit ?? 20), 100);
const cursor = decodeCursor(req.query.cursor); // { id } | null
const rows = await db.query(
`SELECT * FROM articles
WHERE ($1::text IS NULL OR id < $1)
ORDER BY id DESC
LIMIT $2 + 1`,
[cursor?.id ?? null, limit],
);
const hasMore = rows.length > limit;
const items = rows.slice(0, limit);
const nextCursor = hasMore ? encodeCursor({ id: items.at(-1)!.id }) : null;
res.json({ items, next_cursor: nextCursor, has_more: hasMore });
Client consumption (Kotlin, OkHttp):
suspend fun loadPage(cursor: String? = null): Page<Article> {
val url = "https://api.example.com/v1/articles".toHttpUrl().newBuilder()
.addQueryParameter("limit", "20")
.apply { cursor?.let { addQueryParameter("cursor", it) } }
.build()
return http.get(url).parse<Page<Article>>()
}
Default page size 20, max 100. Clamp silently and echo the effective size.
3. Filtering and Sorting
Use flat query parameters, not nested JSON:
GET /v1/articles?author_id=123&tag=mobile&sort=-published_at
- Prefix with
-for descending (sort=-published_at). - Allow comma-separated multi-sort:
sort=-published_at,title. - Restrict sortable and filterable fields to an allowlist; reject others with
400 INVALID_QUERY. - For ranges, use
min_/max_prefixes:min_published_at=2026-01-01.
4. Partial Responses (Sparse Fieldsets)
Mobile screens rarely need every field. Support a fields parameter to reduce payload:
GET /v1/articles?fields=id,title,author.name
Server applies a projection before serialization. Unknown field names are ignored with a Warning header rather than rejecting the request.
5. Consistent Error Envelope
Every non-2xx response uses the same shape:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "title must not be empty",
"details": { "field": "title" },
"request_id": "01HX7..."
}
}
code is stable across minor versions. Clients match on code, never on message.
6. Timestamps, Money, and Enums
- Timestamps: RFC 3339 strings in UTC. Never Unix seconds without units.
- Money: integer minor units plus ISO 4217 currency (
{ "amount": 1299, "currency": "USD" }). - Enums: lowercase_snake strings; clients bucket unknown values as
unknown.
7. Request Identification
Accept X-Request-Id from the client; generate one if missing and echo it. Clients log both their generated id and the server id for cross-referencing.
8. Compression and Caching
Enable gzip and br responses. Set Cache-Control: private, max-age=60 on list reads when data tolerance allows; use ETag + If-None-Match on detail reads to avoid re-downloading unchanged bodies.
Checklist
- Every list endpoint paginated with opaque cursor and documented default/max limits.
- Sparse fieldsets supported on payloads > ~2 KB.
- All non-2xx responses use the shared error envelope with
code,message,request_id. - Sortable and filterable fields are allowlisted and documented.
- Timestamps are RFC 3339 UTC; money uses minor units + currency.
- Unknown enum values documented as bucketed to
unknownon the client. -
X-Request-Idhonored and echoed. -
ETag/Cache-Controlset on cacheable reads.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.