Mobile api design
Skill almasumdev/awesome-mobile-backend-agent-skills/.github/skills/api/mobile-api-design
Agent skills for the backend-for-mobile layer: APIs, auth, push, sync, and BaaS integrations.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
4.5 KB, 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.