{"id":13507812,"url":"https://github.com/api-hogs/bureaucrat","last_synced_at":"2026-02-27T19:18:38.431Z","repository":{"id":36178551,"uuid":"40482680","full_name":"api-hogs/bureaucrat","owner":"api-hogs","description":"Generate Phoenix API documentation from tests","archived":false,"fork":false,"pushed_at":"2025-06-04T13:42:37.000Z","size":432,"stargazers_count":368,"open_issues_count":6,"forks_count":77,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-10-21T14:54:11.869Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/bureaucrat/api-reference.html","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/api-hogs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2015-08-10T13:00:30.000Z","updated_at":"2025-09-30T12:58:02.000Z","dependencies_parsed_at":"2024-01-23T10:14:33.130Z","dependency_job_id":"61b58de0-f6b0-49f7-b041-73f2c269f9b6","html_url":"https://github.com/api-hogs/bureaucrat","commit_stats":{"total_commits":121,"total_committers":36,"mean_commits":3.361111111111111,"dds":0.8016528925619835,"last_synced_commit":"a0b02af15c8941a00010d047b63af749e60dcefb"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/api-hogs/bureaucrat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/api-hogs%2Fbureaucrat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/api-hogs%2Fbureaucrat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/api-hogs%2Fbureaucrat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/api-hogs%2Fbureaucrat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/api-hogs","download_url":"https://codeload.github.com/api-hogs/bureaucrat/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/api-hogs%2Fbureaucrat/sbom","scorecard":{"id":202363,"data":{"date":"2025-08-11","repo":{"name":"github.com/api-hogs/bureaucrat","commit":"119b757d9bdce82e3a5a3779c9948239055ea94e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":1,"reason":"2 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":9,"reason":"Found 23/25 approved changesets -- score normalized to 9","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: UNLICENSE:0","Info: FSF or OSI recognized license: The Unlicense: UNLICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"14 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-2p68-f74v-9wc6","Warn: Project is vulnerable to: GHSA-j6gc-792m-qgm2","Warn: Project is vulnerable to: GHSA-pj73-v5mw-pm9j","Warn: Project is vulnerable to: GHSA-34hf-g744-jw64","Warn: Project is vulnerable to: GHSA-mqm2-cgpr-p4m6","Warn: Project is vulnerable to: GHSA-7g2v-jj9q-g3rg","Warn: Project is vulnerable to: GHSA-7wqh-767x-r66v","Warn: Project is vulnerable to: GHSA-8cgq-6mh2-7j6v","Warn: Project is vulnerable to: GHSA-gjh7-p2fx-99vx","Warn: Project is vulnerable to: GHSA-vpfw-47h7-xj4g","Warn: Project is vulnerable to: GHSA-mqcp-p2hv-vw6x","Warn: Project is vulnerable to: GHSA-p8f7-22gq-m7j9","Warn: Project is vulnerable to: GHSA-5g2h-9x5v-5h3x","Warn: Project is vulnerable to: GHSA-j3gg-r6gp-95q2"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T23:03:32.950Z","repository_id":36178551,"created_at":"2025-08-16T23:03:32.950Z","updated_at":"2025-08-16T23:03:32.950Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29909496,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T17:28:36.873Z","status":"ssl_error","status_checked_at":"2026-02-27T17:28:20.970Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T02:00:39.787Z","updated_at":"2026-02-27T19:18:38.410Z","avatar_url":"https://github.com/api-hogs.png","language":"Elixir","funding_links":[],"categories":["Documentation"],"sub_categories":[],"readme":"# Bureaucrat\n\nBureaucrat is a library that lets you generate API documentation of your Phoenix\napp from tests.\n\n## Installation\n\nFirst, add Bureaucrat to your `mix.exs` dependencies:\n\n```elixir\ndefp deps do\n  [{:bureaucrat, \"~\u003e 0.2.10\"}]\nend\n```\n\nBureaucrat needs a json library and defaults to Poison. It must be added as a dependency:\n\n```elixir\ndefp deps do\n  [\n    {:bureaucrat, \"~\u003e 0.2.10\"},\n    {:poison, \"~\u003e 3.0\"}\n  ]\nend\n```\n\nThen, update your dependencies:\n\n```\n$ mix deps.get\n```\n\nNext, in your `test/test_helper.exs` you should start Bureaucrat and configure\nExUnit to use its formatter. You would probably like to keep the default\n`ExUnit.CLIFormatter` as well.\n\n```elixir\nBureaucrat.start\nExUnit.start(formatters: [ExUnit.CLIFormatter, Bureaucrat.Formatter])\n```\n\nAnd finally, import Bureaucrat helpers in `test/support/conn_case.ex`:\n\n```elixir\ndefmodule Spell.ConnCase do\n  using do\n    quote do\n      import Bureaucrat.Helpers\n    end\n  end\nend\n```\n\nTo generate Phoenix channel documentation, import the helpers in `test/support/channel_case.ex` alike.\n\n## Usage\n\nBureaucrat collects data from connection structs used in tests.\nIf you want a connection to be documented, pass it to the `doc/1` function:\n\n```elixir\ntest \"GET /api/v1/products\" do\n  conn = conn\n      |\u003e get(\"/api/v1/products\")\n      |\u003e doc()\n  assert conn.status == 200\nend\n```\n\nAdditional options can be passed to the backend formatter:\n\n```elixir\ntest \"GET /api/v1/products\" do\n  conn = conn\n      |\u003e get(\"/api/v1/products\")\n      |\u003e doc(description: \"List all products\", operation_id: \"list_products\")\n  assert conn.status == 200\nend\n\n```\n\nThen, to generate the documentation file(s) run `DOC=1 mix test`.\nThe default output file is `API.md` in the project root.\n\n### Pipe `|\u003e doc()` automatically\nIf you don't want to call `|\u003e doc()` on each request, you can import `Bureaucrat.Macros`.\n\n- It automatically adds `|\u003e doc()` to the `Phoenix.ConnTest` macros\n- It creates other macros: `get_undocumented`, `post_undocumented`, `patch_undocumented`, `put_undocumented` and `delete_undocumented`, to be used in requests you want to skip docs\n\nTo achieve this, replace\n\n```elixir\nimport Phoenix.ConnTest\n```\n\nBy\n\n```elixir\nimport Phoenix.ConnTest, only: :functions\nimport Bureaucrat.Helpers\nimport Bureaucrat.Macros\n```\n\n### Custom intro sections\n\nTo add a custom intro section, for each output file, bureaucrat will look for an **intro markdown file** in the output directory,\nnamed like the output file with a `_intro` or `_INTRO` suffix (before `.md`, if present), e.g.\n\n- `web/controllers/README` -\u003e `web/controllers/README_INTRO`\n- `web/controllers/readme.md` -\u003e `web/controllers/readme_intro.md`\n\nCurrently the supported writers are the default `Bureaucrat.MarkdownWriter` and `Bureaucrat.ApiBlueprintWriter`.\n\n## Documenting Phoenix Channels\n\nBureaucrat can also generate documentation for messages, replies and broadcasts in [Phoenix Channels](http://www.phoenixframework.org/docs/channels).\n\nResults of `assert_push`, `assert_broadcast` and the underlying `assert_receive` (if used for messages or broadcasts) can be passed to the `doc` function.\n\nTo document usage of [Phoenix.ChannelTest](https://hexdocs.pm/phoenix/Phoenix.ChannelTest.html) helpers `connect`, `push`, `broadcast_from` and `broadcast_from!`, Bureaucrat includes documenting alternatives, prefixed with `doc_`:\n\n- `doc_connect`\n- `doc_push`\n- `doc_broadcast_from`\n- `doc_broadcast_from!`\n\n```elixir\ntest \"message:new broadcasts are pushed to the client\", %{socket: socket} do\n  doc_broadcast_from! socket, \"message:new\", %{body: \"Hello there!\", timestamp: 1483971926566, user: \"marla\"}\n  assert_push(\"message:new\", %{body: \"Hello there!\", timestamp: 1483971926566, user: \"marla\"})\n  |\u003e doc()\nend\n```\n\nChannels docs output is currently only supported by the `Bureaucrat.MarkdownWriter` and only to the `default_path` (see [Configuration](#configuration) below).\n\n## Swagger \u0026 Slate Integration\n\nBureaucrat comes with the `Bureaucrat.SwaggerSlateMarkdownWriter` backend that will merge test examples with a swagger spec to produce markdown files that can be processed with the [slate](https://github.com/lord/slate) static generator.\n\nTo configure swagger integration, first write a swagger file by hand or generate one using [phoenix_swagger](https://github.com/xerions/phoenix_swagger). In the example below, the swagger file exists in the project at `priv/static/swagger.json`.\n\nClone the slate project into a directory in your project:\n\n```\ngit clone --shallow https://github.com/lord/slate doc\n```\n\nConfigure Bureaucrat `writer`, `default_path` and `swagger`:\n\n```elixir\nBureaucrat.start(\n  env_var: \"DOC\",\n  writer: Bureaucrat.SwaggerSlateMarkdownWriter,\n  default_path: \"doc/source/index.html.md\",\n  swagger: \"priv/static/swagger.json\" |\u003e File.read!() |\u003e Poison.decode!())\n```\n\nWithin each test, link the test example to a swagger operation by passing an `operation_id` to the `doc` helper:\n\n```elixir\ntest \"creates and renders resource when data is valid\", %{conn: conn} do\n  conn =\n    conn\n    |\u003e post(user_path(conn, :create), user: @valid_attrs)\n    |\u003e doc(operation_id: \"create_user\")\n\n  assert json_response(conn, 201)[\"data\"][\"id\"]\n  assert Repo.get_by(User, @valid_attrs)\nend\n```\n\nNow generate documentation with `DOC=1 mix test`.\n\nUse slate to convert the markdown to HTML:\n\n```\ncd doc\nbundle install\nbundle exec middleman build\n```\n\nTo serve the documentation directly from your application, copy the slate build output to your `priv/static` directory:\n\n```\nmkdir priv/static/doc\ncp -R doc/build/* priv/static/doc\n```\n\nWhitelist the `doc` directory for static assets in the `Plug.Static` configuration:\n\n```elixir\nplug Plug.Static,\n  at: \"/\", from: :swagger_demo, gzip: false,\n  only: ~w(css doc fonts images js favicon.ico robots.txt)\n```\n\nRun your application with `mix phoenix.server` and visit `http://localhost:4000/doc/index.html` to see your documentation.\n\nFor a full example see the `examples/swagger_demo` project.\n\n## API Blueprint support\n\nBureaucrat also supports generating markdown files that are formatted in the [API Blueprint](https://apiblueprint.org/) syntax.\nSimply set the `Bureaucrat.ApiBlueprintWriter` in your configuration file and run the usual:\n\n```\nDOC=1 mix test\n```\n\nAfter the markdown file has been successfully generated you can use [aglio](https://github.com/danielgtaylor/aglio) to produce the html file:\n\n```\naglio -i web/controllers/api/v1/documentation.md -o web/controllers/api/v1/documentation.html\n```\n\n### API Blueprint usage note\n\nIf you're piping through custom plugs than can prevent the HTTP requests to land in the controllers (authentication, authorization) and you want to document these cases you'll need the `plug_doc()` helper:\n\n```\ndescribe \"unauthenticated user\" do\n  test \"GET all items\", %{conn: conn} do\n    conn\n    |\u003e get(item_path(conn, :index))\n    |\u003e plug_doc(module: __MODULE__, action: :index)\n    |\u003e doc()\n    |\u003e assert_unauthenticated()\n  end\nend\n```\n\nWithout the `plug_doc()` helper Bureaucrat doesn't know the `phoenix_controller` (since the request never landed in the controller) and an error is raised: `** (RuntimeError) GET all items (/api/v1/items) doesn't have required :phoenix_controller key. Have you forgotten to plug_doc()?`\n\n## Postman support\n\nBureaucrat also supports generating json files that are formatted in the\n[Postman Collection v2.1](https://schema.postman.com/json/collection/v2.1.0/docs/index.html) schema.\n\nIt writes one folder per controller, one request per action and one response per test example.\nExUnit test descriptions are used as response example names, along with the response status.\nParams/query/body keys that are ended in `_id` have values substituted by environment `{{variables}}`.\nSets the collection name as the json filename specified in `path` configuration.\nSupports bearer authentication header.\n\n```elixir\nBureaucrat.start(\n  writer: Bureaucrat.PostmanWriter,\n  prefix: \"Elixir.MyAppWeb\",\n  default_path: \"docs/my_app.json\"\n)\n```\n\nSupports all configurations but not custom `titles` or intro files.\nUse `prefix` to trim the prefix out of the Postman folder names.\n\n## Configuration\n\nThe configuration options can be passed to `Bureaucrat.start`:\n\n```elixir\nBureaucrat.start(\n writer: Bureaucrat.MarkdownWriter,\n default_path: \"web/controllers/README.md\",\n paths: [],\n titles: [],\n env_var: \"DOC\",\n json_library: Poison\n)\n```\n\nThe available options are:\n\n- `:writer`: The module used to generate docs from the list of captured\n  connections.\n- `:default_path`: The path where the docs are written by default.\n- `:paths`: Allows you to specify different doc paths for some of your modules.\n  For example `[{YourApp.Api.V1, \"web/controllers/api/v1/README.md\"}]` will\n  cause the docs for controllers under `YourApp.Api.V1` namespace to\n  be written to `web/controllers/api/v1/README.md`.\n- `:titles`: Allows you to specify explicit titles for some of your modules.\n  For example `[{YourApp.Api.V1.UserController, \"API /v1/users\"}]` will\n  change the title (Table of Contents entry and heading) for this controller.\n- `:prefix`: Allows you to remove the prefix of the test module names\n- `:env_var`: The environment variable used as a flag to trigger doc generation.\n- `:json_library`: The JSON library to use. Poison (the default) and Jason both work.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapi-hogs%2Fbureaucrat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapi-hogs%2Fbureaucrat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapi-hogs%2Fbureaucrat/lists"}