jsonapi
SKILL.md
Use With django-drf
This skill focuses on spec compliance. For implementation patterns (ViewSets, Serializers, Filters), use django-drf skill together with this one.
| Skill | Focus |
|---|---|
jsonapi |
What the spec requires (MUST/MUST NOT rules) |
django-drf |
How to implement it in DRF (code patterns) |
When creating/modifying endpoints, invoke BOTH skills.
Before Implementing/Reviewing
ALWAYS validate against the latest spec before creating or modifying endpoints:
Option 1: Context7 MCP (Preferred)
If Context7 MCP is available, query the JSON:API spec directly:
mcp_context7_resolve-library-id(query="jsonapi specification")
mcp_context7_query-docs(libraryId="<resolved-id>", query="[specific topic: relationships, errors, etc.]")
Option 2: WebFetch (Fallback)
If Context7 is not available, fetch from the official spec:
WebFetch(url="https://jsonapi.org/format/", prompt="Extract rules for [specific topic]")
This ensures compliance with the latest JSON:API version, even after spec updates.
Critical Rules (NEVER Break)
Document Structure
- NEVER include both
dataanderrorsin the same response - ALWAYS include at least one of:
data,errors,meta - ALWAYS use
typeandid(string) in resource objects - NEVER include
idwhen creating resources (server generates it)
Content-Type
- ALWAYS use
Content-Type: application/vnd.api+json - ALWAYS use
Accept: application/vnd.api+json - NEVER add parameters to media type without
ext/profile
Resource Objects
- ALWAYS use string for
id(even if UUID) - ALWAYS use lowercase kebab-case for
type - NEVER put
idortypeinsideattributes - NEVER include foreign keys in
attributes- userelationships
Relationships
- ALWAYS include at least one of:
links,data, ormeta - ALWAYS use resource linkage format:
{"type": "...", "id": "..."} - NEVER use raw IDs in relationships - always use linkage objects
Error Objects
- ALWAYS return errors as array:
{"errors": [...]} - ALWAYS include
statusas string (e.g.,"400", not400) - ALWAYS include
source.pointerfor field-specific errors
HTTP Status Codes (Mandatory)
| Operation | Success | Async | Conflict | Not Found | Forbidden | Bad Request |
|---|---|---|---|---|---|---|
| GET | 200 |
- | - | 404 |
403 |
400 |
| POST | 201 |
202 |
409 |
404 |
403 |
400 |
| PATCH | 200 |
202 |
409 |
404 |
403 |
400 |
| DELETE | 200/204 |
202 |
- | 404 |
403 |
- |
When to Use Each
| Code | Use When |
|---|---|
200 OK |
Successful GET, PATCH with response body, DELETE with response |
201 Created |
POST created resource (MUST include Location header) |
202 Accepted |
Async operation started (return task reference) |
204 No Content |
Successful DELETE, PATCH with no response body |
400 Bad Request |
Invalid query params, malformed request, unknown fields |
403 Forbidden |
Authentication ok but no permission, client-generated ID rejected |
404 Not Found |
Resource doesn't exist OR RLS hides it (never reveal which) |
409 Conflict |
Duplicate ID, type mismatch, relationship conflict |
415 Unsupported |
Wrong Content-Type header |
Document Structure
Success Response (Single)
{
"data": {
"type": "providers",
"id": "550e8400-e29b-41d4-a716-446655440000",
"attributes": {
"alias": "Production",
"connected": true
},
"relationships": {
"tenant": {
"data": {"type": "tenants", "id": "..."}
}
},
"links": {
"self": "/api/v1/providers/550e8400-..."
}
},
"links": {
"self": "/api/v1/providers/550e8400-..."
}
}
Success Response (List)
{
"data": [
{"type": "providers", "id": "...", "attributes": {...}},
{"type": "providers", "id": "...", "attributes": {...}}
],
"links": {
"self": "/api/v1/providers?page[number]=1",
"first": "/api/v1/providers?page[number]=1",
"last": "/api/v1/providers?page[number]=5",
"prev": null,
"next": "/api/v1/providers?page[number]=2"
},
"meta": {
"pagination": {"count": 100, "pages": 5}
}
}
Error Response
{
"errors": [
{
"status": "400",
"code": "invalid",
"title": "Invalid attribute",
"detail": "UID must be 12 digits for AWS accounts",
"source": {"pointer": "/data/attributes/uid"}
}
]
}
Query Parameters
| Family | Format | Example |
|---|---|---|
page |
page[number], page[size] |
?page[number]=2&page[size]=25 |
filter |
filter[field], filter[field__op] |
?filter[status]=FAIL |
sort |
Comma-separated, - for desc |
?sort=-inserted_at,name |
fields |
fields[type] |
?fields[providers]=id,alias |
include |
Comma-separated paths | ?include=provider,scan.task |
Rules
- MUST return
400for unsupported query parameters - MUST return
400for unsupportedincludepaths - MUST return
400for unsupportedsortfields - MUST NOT include extra fields when
fields[type]is specified
Common Violations (AVOID)
| Violation | Wrong | Correct |
|---|---|---|
| ID as integer | "id": 123 |
"id": "123" |
| Type as camelCase | "type": "providerGroup" |
"type": "provider-groups" |
| FK in attributes | "tenant_id": "..." |
"relationships": {"tenant": {...}} |
| Errors not array | {"error": "..."} |
{"errors": [{"detail": "..."}]} |
| Status as number | "status": 400 |
"status": "400" |
| Data + errors | {"data": ..., "errors": ...} |
Only one or the other |
| Missing pointer | {"detail": "Invalid"} |
{"detail": "...", "source": {"pointer": "..."}} |
Relationship Updates
To-One Relationship
PATCH /api/v1/providers/123/relationships/tenant
Content-Type: application/vnd.api+json
{"data": {"type": "tenants", "id": "456"}}
To clear: {"data": null}
To-Many Relationship
| Operation | Method | Body |
|---|---|---|
| Replace all | PATCH | {"data": [{...}, {...}]} |
| Add members | POST | {"data": [{...}]} |
| Remove members | DELETE | {"data": [{...}]} |
Compound Documents (include)
When using ?include=provider:
{
"data": {
"type": "scans",
"id": "...",
"relationships": {
"provider": {
"data": {"type": "providers", "id": "prov-123"}
}
}
},
"included": [
{
"type": "providers",
"id": "prov-123",
"attributes": {"alias": "Production"}
}
]
}
Rules
- Every included resource MUST be reachable via relationship chain from primary data
- MUST NOT include orphan resources
- MUST NOT duplicate resources (same type+id)
Spec Reference
- Full Specification: https://jsonapi.org/format/
- Implementation: Use
django-drfskill for DRF-specific patterns - Testing: Use
prowler-test-apiskill for test patterns
Weekly Installs
6
Repository
prowler-cloud/prowlerInstalled on
claude-code5
windsurf4
opencode4
cursor4
kiro-cli4
codex4