Skip to main content

A small, typed JSON frame (envelope) for API responses and messages.

Project description

jsonframe

A tiny, opinionated library for consistent JSON API response frames.

jsonframe standardizes how APIs return successful responses, collections, pagination metadata, and errors — without dragging in heavy specs or forcing a framework.


Design goals

  • Responses are always JSON objects (never top-level arrays)
  • Predictable structure across services
  • Minimal cognitive load for newcomers
  • No success: true flags — HTTP status codes already exist
  • Small enough to understand in one sitting

Core response rules

Success

{
  "data": ...,
  "meta": { ... }
}
  • data contains the business payload (object, list, scalar, or null)
  • meta contains non-business metadata (optional, always an object). Typical examples include pagination info, request IDs, timing data, or feature flags — never domain data.

Error

{
  "detail": "Invalid request payload"
}

Or structured:

{
  "detail": {
    "code": "validation_error",
    "message": "Invalid request payload",
    "meta": { ... }
  }
}
  • Errors are represented by a single error object (no arrays, no partial failures)
  • HTTP status code communicates severity
  • code is always present in structured form (value can be null)
  • meta is optional and always an object when present

Examples

Example of success payload and framed result

Given the user object and request_id:

from jsonframe import ok

user = {
  "id": 42,
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "role": "admin"
}

result = ok(data=user, meta={"request_id": "req_123"})

Result:

{
  "data": {
    "id": 42,
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "role": "admin"
  },
  "meta": {
    "request_id": "req_123"
  }
}

Example error response (string)

from jsonframe import error

result = error(message="User not found")
{
  "detail": "User not found"
}

Example error response (structured)

from jsonframe import error

result = error(
    code="not_found",
    message="User not found",
    meta={"request_id": "req_123"},
)
{
  "detail": {
    "code": "not_found",
    "message": "User not found",
    "meta": {
      "request_id": "req_123"
    }
  }
}

Installation

uv add jsonframe

Core dependency:

  • pydantic >= 2.0 (used for lightweight validation and serialization)

Usage

Success response

from jsonframe import ok

return ok(data={"id": 1, "name": "Ada"})

Empty success

from jsonframe import ok

return ok()

List response

from jsonframe import ok

return ok(data=[{"id": 1}, {"id": 2}])

Paginated list

from jsonframe import ok

return ok(
    data=[{"id": 1}, {"id": 2}],
    meta={"page": {"total": 120, "limit": 20, "offset": 40}},
)

Result:

{
  "data": [...],
  "meta": {
    "page": {
      "total": 120,
      "limit": 20,
      "offset": 40
    }
  }
}

Error response

from jsonframe import error

return error(
    message="Invalid input",
    code="validation_error",
    meta={"field": "email"},
)

Using ErrorDetail with FastAPI

ErrorDetail produces the right shape for FastAPI's HTTPException.detail:

from fastapi import HTTPException
from jsonframe import ErrorDetail

raise HTTPException(
    status_code=404,
    detail=ErrorDetail(
        message="User not found",
        code="not_found",
        meta={"user_id": 42},
    ).to_dict(),
)

FastAPI will return:

{
  "detail": {
    "code": "not_found",
    "message": "User not found",
    "meta": {
      "user_id": 42
    }
  }
}

For simple string errors:

raise HTTPException(
    status_code=400,
    detail=ErrorDetail(message="Bad request").to_dict(),
)

Returns:

{
  "detail": "Bad request"
}

What jsonframe is not

  • Not a full JSON:API implementation
  • Not a validation framework
  • Not a transport abstraction
  • Not a replacement for OpenAPI or HTTP semantics

When to use jsonframe

  • Internal APIs
  • BFFs
  • Microservices
  • AI / LLM-backed services
  • Teams that want consistency without ceremony

Philosophy

jsonframe is intentionally small.

It standardizes structure, not business logic. If you can't explain your API responses by pointing to this README, the library is doing too much.


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jsonframe-0.3.2.tar.gz (4.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jsonframe-0.3.2-py3-none-any.whl (6.0 kB view details)

Uploaded Python 3

File details

Details for the file jsonframe-0.3.2.tar.gz.

File metadata

  • Download URL: jsonframe-0.3.2.tar.gz
  • Upload date:
  • Size: 4.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for jsonframe-0.3.2.tar.gz
Algorithm Hash digest
SHA256 84a15512c0453b750cbaf965e93f84cf1614db8321e51cf9881c2f67d9e858b9
MD5 24f75723abdc0e3b5c996fa488891f72
BLAKE2b-256 c55a9d2a6bb0866156d30e131f7f75435d9823a5fafebbe599dbd93537cd69f2

See more details on using hashes here.

File details

Details for the file jsonframe-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: jsonframe-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 6.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for jsonframe-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0aacd82e9126a9158bbaf0c188cd6f9dd1eec08a54bec1db352dfaa6ff7d0dad
MD5 477ffa0bb344cd6bf71208e8afcc6a4e
BLAKE2b-256 dd14dc11d555d5ee61fbd4eefbe9d58d7edf94a1ea3f0fcb2c3584af23f05caf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page