{"id":50895027,"url":"https://github.com/burin-labs/harn-openapi","last_synced_at":"2026-06-15T23:30:44.086Z","repository":{"id":352658475,"uuid":"1216018870","full_name":"burin-labs/harn-openapi","owner":"burin-labs","description":"Pure-Harn OpenAPI 3.1 parser and SDK codegen helpers","archived":false,"fork":false,"pushed_at":"2026-06-13T04:53:53.000Z","size":360,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T06:26:02.910Z","etag":null,"topics":["harn","harn-package","openapi"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/burin-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-20T13:37:23.000Z","updated_at":"2026-06-06T02:16:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/burin-labs/harn-openapi","commit_stats":null,"previous_names":["burin-labs/harn-openapi"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/burin-labs/harn-openapi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/burin-labs%2Fharn-openapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/burin-labs%2Fharn-openapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/burin-labs%2Fharn-openapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/burin-labs%2Fharn-openapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/burin-labs","download_url":"https://codeload.github.com/burin-labs/harn-openapi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/burin-labs%2Fharn-openapi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34385030,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["harn","harn-package","openapi"],"created_at":"2026-06-15T23:30:42.534Z","updated_at":"2026-06-15T23:30:44.076Z","avatar_url":"https://github.com/burin-labs.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# harn-openapi\n\nPure-Harn library for parsing OpenAPI 3.1 documents and generating typed Harn\nSDK source code from them. Acts as the reference example of a non-trivial\nexternal Harn library and powers downstream typed SDKs such as\n[notion-sdk-harn](https://github.com/burin-labs/notion-sdk-harn).\n\n\u003e Status: `0.1.1-rc.1` pre-release package shape. Until a release tag exists, use a\n\u003e path dependency or explicit `@HEAD` package smoke for unreleased work.\n\n## Intent\n\n`harn-openapi` is the OpenAPI layer for Harn's external connector ecosystem.\nProvider-specific SDKs and connectors should not each learn OpenAPI parsing,\nschema walking, security handling, or typed SDK generation independently. This\nrepo keeps that shared logic in one pure-Harn package:\n\n- parse OpenAPI 3.1 JSON into normalized Harn records;\n- walk paths, webhooks, components, schemas, enums, security metadata,\n  pagination patterns, and rate-limit response conventions;\n- generate typed Harn SDK source for downstream provider repos;\n- keep a pinned real-world Notion OpenAPI fixture for deterministic coverage.\n\nThe generator is intentionally scoped to focused API packages backed by an\nOpenAPI document. Broad cloud SDK families, provider discovery formats, Smithy,\nand provider-specific auth flows belong in dedicated packages above this layer,\nnot in `harn-openapi`.\n\nThe repo intentionally has no Cargo workspace, `package.json`, generated build\nsystem, or non-Harn runtime dependency. CI and local development use the Harn\nCLI pinned by `.harn-version`.\n\n## Repository layout\n\n| Path | Purpose |\n|---|---|\n| `harn.toml` | Package metadata and exported entry points. `src/lib.harn` is the default export and carries the typed public surface. |\n| `src/lib.harn` | Public parser, walker, schema-resolution, and SDK codegen implementation. |\n| `tests/*.harn` | Smoke and behavior tests for parsing, walking, codegen, security, response typing, polymorphic request bodies, fixture tooling, and helper scripts. |\n| `tests/fixtures/notion.openapi.json` | Pinned Notion OpenAPI 3.1 snapshot used as the main real-world fixture. |\n| `tests/fixtures/notion.openapi.json.meta.toml` | Capture metadata for the pinned fixture: upstream URL, timestamp, byte size, and SHA-256. |\n| `tests/fixtures/connector_helpers.openapi.json` | Small synthetic OpenAPI fixture covering auth alternatives, pagination, and rate-limit metadata. |\n| `scripts/regen_demo.harn` | End-to-end parse to codegen demo that writes generated SDK source to `/tmp`. |\n| `scripts/refresh_fixtures.harn` | Refreshes the pinned Notion fixture and metadata intentionally. |\n| `scripts/fixture_diff.harn` | Prints a structured operation/schema diff between two fixture captures. |\n| `scripts/check_fixture_staleness.harn` | CI guard for fixture age. |\n| `scripts/package_install_smoke.harn` | Clean temp-project install/import smoke for package-manager consumption. |\n| `.harn-version` | Pinned Harn CLI version used by CI and local gates. |\n| `scripts/bump_harn_cli_version.harn` | Updates the pinned Harn CLI version and runs the local gate. |\n| `.github/workflows/ci.yml` | Harn check/lint/fmt/package/test/demo/fixture workflow with an aggregate `CI status` job for branch rules and merge queue. |\n| `docs/migration-v0.1.0.md` | Migration note for connector repos moving from sibling path imports to package-managed imports. |\n| `AGENTS.md` | Repo-specific instructions for coding agents. |\n\n## Install\n\nFor unreleased local or stacked work, use a path dependency:\n\n```toml\n[dependencies]\nharn-openapi = { path = \"../harn-openapi\" }\n```\n\nAfter the `v0.1.1-rc.1` release tag exists, consumers can use the versioned package\nref:\n\n```sh\nharn add github.com/burin-labs/harn-openapi@v0.1.1-rc.1\n```\n\nThe CI package smoke uses the same package-manager path against the current\ncheckout (`\u003crepo\u003e@HEAD`) so pull requests are checked before a release tag\nexists. To test a published ref locally after tagging:\n\n```sh\nHARN_PACKAGE_REF=github.com/burin-labs/harn-openapi@v0.1.1-rc.1 \\\n  harn run scripts/package_install_smoke.harn\n```\n\n## Usage\n\nWith either package install path, import the named functions you use:\n\n```harn\nimport {\n  parse,\n  operations,\n  webhook_operations,\n  component_path_items,\n  auth_helpers,\n  pagination_plans,\n  rate_limit_metadata,\n  codegen_module,\n  codegen_harn_toml,\n} from \"harn-openapi/default\"\n\nlet raw = read_file(\"./notion.openapi.json\")\nlet doc = parse(raw)\n\n// Walk operations\nlet ops = operations(doc)\nfor op in ops {\n  println(\"${op.method} ${op.path} -\u003e ${op.operation_id}\")\n}\n\n// Walk OAS 3.1 webhooks (inbound payloads)\nfor wop in webhook_operations(doc) {\n  println(\"webhook ${wop.name} (${wop.method}) -\u003e ${wop.operation_id}\")\n}\n\n// Passthrough accessor for the 3.1-new components.pathItems map\nlet shared_path_items = component_path_items(doc)\n\n// Connector-facing helper metadata\nlet auth = auth_helpers(doc)\nlet pages = pagination_plans(doc)\nlet limits = rate_limit_metadata(doc)\n\n// Generate Harn SDK source from the parsed document\nlet src = codegen_module(doc, {\n  module_name: \"notion\",\n  client_name: \"Client\",\n  // Optional. Defaults to raw http_get/post/... calls.\n  transport: \"connector_policy\",\n})\nwrite_file(\"./src/lib.harn\", src)\nwrite_file(\n  \"./harn.toml\",\n  codegen_harn_toml({\n    package_name: \"notion-sdk-harn\",\n    dependencies: {[\"harn-openapi\"]: {path: \"../harn-openapi\"}},\n  }),\n)\n```\n\nWhen running examples or tests directly from this repository checkout, use the\nsource path instead:\n\n```harn\nimport { parse } from \"../src/lib\"\n```\n\n### Exported surface\n\n- `parse(json_string) -\u003e OpenApiDoc` — normalize a 3.1.x doc to\n  `{ openapi, info, servers, paths, webhooks, components,\n  security_schemes, security, tags }`. `paths` may be empty (3.1 allows\n  webhooks-only docs). `components.pathItems` is always present as a\n  dict, even when the source doc omits it.\n- `operations(doc: OpenApiDoc) -\u003e list\u003cOperation\u003e` — flatten `doc.paths` into operation\n  records `{ method, path, operation_id, summary, parameters,\n  request_body, responses, security, tags, ... }`. Each operation's\n  `security` is resolved as `op.security ?? doc.security ?? []`.\n- `webhook_operations(doc: OpenApiDoc) -\u003e list\u003cWebhookOperation\u003e` — flatten `doc.webhooks` into\n  records `{ name, method, path_item, operation, operation_id,\n  summary, parameters, request_body, responses, security, tags }`.\n  `name` is the webhook key (e.g. `commentCreated`); downstream\n  connectors use these to know which inbound payloads to handle.\n- `component_path_items(doc: OpenApiDoc) -\u003e dict\u003cstring, RefOr\u003cPathItem\u003e\u003e` —\n  passthrough accessor for `doc.components.pathItems` (new in\n  OAS 3.1). Returns `{}` when absent.\n- `schema(doc: OpenApiDoc, ref_or_inline: RefOr\u003cSchema\u003e) -\u003e Schema` — resolve a local `$ref`, or\n  return inline schemas unchanged. Merges one level of `allOf` and preserves\n  `$ref` siblings as valid JSON Schema 2020-12 data.\n- `enum_values(schema: Schema) -\u003e list\u003cstring\u003e | nil` — extract the enum variant list,\n  or `nil` when the schema is not an enum.\n- `is_openapi_doc(value)`, `is_reference(value)`, `is_schema(value)` — small schema guards\n  for common dynamic boundaries.\n- `auth_helpers(doc: OpenApiDoc) -\u003e list\u003cAuthHelper\u003e` — classify each declared\n  security scheme into reusable helper metadata, including generated client\n  fields and OAuth scopes where present.\n- `pagination_plans(doc: OpenApiDoc) -\u003e list\u003cPaginationPlan\u003e` — detect simple\n  cursor, page-number, and next-link list patterns from operation query\n  parameters and success-response schemas.\n- `rate_limit_metadata(doc: OpenApiDoc) -\u003e list\u003cRateLimitMetadata\u003e` — surface\n  per-operation 429, `Retry-After`, and `X-RateLimit-*` response header\n  conventions for downstream retry/backoff code.\n- `codegen_module(doc: OpenApiDoc, options: dict) -\u003e string` — emit a typed Harn SDK\n  module source string with per-scheme security dispatch, credential-provider\n  hooks, optional connector-policy transport, pagination metadata, and\n  rate-limit metadata (see below).\n- `codegen_harn_toml(options: dict) -\u003e string` — emit a package manifest for a\n  generated SDK repo with `[package]`, `[exports]`, and `[dependencies]`.\n\n### Compatibility policy\n\nThe v0.1.0 public API is the set of `harn.toml` exports plus the named\nfunctions and types listed above. Patch releases in the `0.1.x` line should not\nremove exports, rename fields in normalized parser output, or change generated\nclient function names for equivalent OpenAPI inputs. New helpers, extra\nnormalized fields, and stricter diagnostics are acceptable patch changes when\nexisting callers continue to check and run.\n\nBreaking API changes before a stable 1.0 release require a new minor version,\na migration note under `docs/`, and fixture-backed smoke coverage showing the\nnew behavior. The supported Harn CLI floor is the version pinned in\n`.harn-version`; CI installs that exact `harn-cli` release from crates.io.\n\n### Security handling in generated clients\n\n`codegen_module` inspects `components.securitySchemes` and emits a\ndedicated `_headers_\u003cscheme\u003e(client)` helper per scheme that is actually\nreferenced by at least one operation. Each operation dispatches through\nthe helper matching its *effective* security (`op.security ?? doc.security\n?? []`) — explicit `security: []` at either level routes through\n`_no_auth_headers(client)` so the call goes out without an `Authorization`\nheader. A single OpenAPI security requirement object is treated as an AND:\n`security: [{bearerAuth: [], apiKeyAuth: []}]` sends both schemes, while\nseparate objects remain alternatives.\n\nGenerated auth helpers omit missing bearer/basic/header/cookie credentials\ninstead of sending malformed empty auth material. Cookie auth values use the\nsame URL encoding path as normal cookie parameters.\n\n`new_client` takes only the client fields implied by the schemes in use,\nall defaulted so callers only supply what their spec actually needs:\n\n| Scheme kind | Client field added |\n|---|---|\n| `http` + `bearer`, `oauth2`, `openIdConnect` | `token: string = \"\"`, `token_provider = nil` |\n| `http` + `basic` | `basic_user: string = \"\"`, `basic_password: string = \"\"`, `basic_provider = nil` |\n| `apiKey` (header / query / cookie) | `api_keys: dict = {}`, `api_key_provider = nil` (keyed by scheme name) |\n| `mutualTLS` | (v0: no-op — op falls through to `_no_auth_headers`) |\n\nSo a Notion-shaped spec with `bearerAuth` + `basicAuth` yields:\n\n```harn\nnew_client(\n  base_url: string = \"https://api.notion.com\",\n  token: string = \"\",\n  token_provider = nil,\n  basic_user: string = \"\",\n  basic_password: string = \"\",\n  basic_provider = nil,\n  extra_headers: dict = {\"Notion-Version\": \"...\"},\n) -\u003e dict\n```\n\nFor connector packages, prefer provider hooks over static token strings. The\ngenerated module includes `token_from_secret(secret_id)` and\n`api_key_from_secret(secret_ids)` helpers that call Harn's active\n`secret_get(...)` connector primitive at request time. Callers can also pass\ncustom closures for token refresh, OAuth storage, or multi-tenant key lookup.\n\nWhen an operation declares multiple security requirement alternatives\n(`security: [{a: []}, {b: []}]`), v0 picks the first and leaves a\n`NOTE` comment above the generated function listing the alternatives so\na human can retarget manually.\n\nGenerated operation functions include OpenAPI `path`, `query`, `header`, and\n`cookie` parameters in their signatures. Path values are URL-encoded during\ninterpolation, query values are encoded in the query string, header parameters\nare merged into the request headers, and cookie parameters are appended to the\n`Cookie` header.\n\nOut of scope for v0: full OAuth2 flows (authorization-code, device,\nPKCE) and `mutualTLS` client-certificate plumbing.\n\n### Transport policy\n\nGenerated SDKs default to the connector policy transport. It avoids deprecated\nambient HTTP builtins in generated code and routes requests through the shared\nretry, idempotency, JSON parse, and rate-limit policy layer:\n\n```harn\nlet src = codegen_module(doc, {\n  module_name: \"example_sdk\",\n  client_name: \"ExampleClient\",\n  transport: \"connector_policy\",\n})\n```\n\nConnector-policy output imports `connector_http_request` and\n`connector_http_json` from `std/connectors/shared`. JSON response operations\ncall `connector_http_json`; opaque or empty-response operations call\n`connector_http_request`. On helper errors, generated functions throw the\nhelper envelope directly, so callers can branch on `category`, `status`,\n`retryable`, `retry_after_ms`, `error`, and `rate_limit` without parsing a\nstring.\n\nSafe or idempotent methods (`GET`, `HEAD`, `PUT`, `DELETE`, `OPTIONS`) emit a\nbounded retry policy. `POST` and `PATCH` emit that retry policy only when the\nOpenAPI operation declares an explicit `Idempotency-Key` header parameter; the\ngenerated function also threads that parameter into `options.idempotency_key`\nfor the shared helper. Unsafe writes without an idempotency key emit\n`max_attempts: 1`, leaving retries disabled by default.\n\nGenerated packages that still need the historical direct-HTTP shape can opt in\nexplicitly:\n\n```harn\nlet src = codegen_module(doc, {\n  module_name: \"example_sdk\",\n  client_name: \"ExampleClient\",\n  transport: \"raw\",\n})\n```\n\nRaw transport emits direct `http_get`, `http_post`, and sibling calls with\nstructured throws for non-2xx responses. Prefer connector policy for new SDKs.\n\n### Pagination and rate-limit helpers\n\n`pagination_plans(doc)` and generated SDKs expose the same lightweight metadata\nfor list operations. The detector intentionally recognizes only common\nprovider-neutral shapes: cursor params such as `start_cursor` with response\nfields such as `next_cursor`, page params such as `page` / `per_page`, and\nresponse next-link fields such as `next_url`.\n\nGenerated SDKs include `pagination_plans()`, `pagination_plan(operation_id)`,\n`pagination_items(response, plan)`, and `pagination_next(response, plan)`.\n`rate_limit_metadata(doc)` and generated SDKs surface declared `429`,\n`Retry-After`, and `X-RateLimit-*` headers. Generated SDKs also include\n`rate_limit_from_response(resp)` and `is_rate_limited_response(resp)` so\nconnectors can implement retries/backoff consistently without hardcoding every\nendpoint class.\n\n### Polymorphic request bodies\n\nFor `application/json` request bodies whose top-level schema is `oneOf` or\n`anyOf`, `codegen_module` emits the normal umbrella operation plus one\nconstructor per variant. Callers can build the body dynamically and pass it to\nthe umbrella, while static callers can use the variant constructor directly:\n\n```harn\nlet body = update_page_markdown_insert_content({\n  content: \"## New section\",\n})\nlet page = update_page_markdown(client, page_id, body)\n```\n\nThe constructor adds an internal `_variant` tag so the umbrella can validate\nwhich variant was selected, then strips `_variant` before serializing the HTTP\nbody. When an OpenAPI discriminator mapping is present, the mapping key is used\nas the `_variant` tag.\n\nDownstream SDK wrappers can expose the same two styles as methods, e.g.\n`client.update_page_markdown(body)` for dynamic dispatch and\n`client.update_page_markdown_insert_content({...})` for a static variant call.\n\n## Development\n\nRead [AGENTS.md](./AGENTS.md) before making changes. It has the current agent\nrules, local gate, and fixture guidance.\n\nThe upstream Harn language and runtime live at\n[burin-labs/harn](https://github.com/burin-labs/harn). For local development,\nclone it next to this repo at `../harn`.\n\n### Local checks\n\nThe GitHub workflows install the Harn version pinned by `.harn-version`, using\nthe published release archive when available and falling back to crates.io.\nLocal development can use either a released `harn` binary or the upstream\ncheckout. With a released binary:\n\n```sh\nharn check src scripts\nharn lint src scripts\nharn fmt --check src scripts tests\nharn package check\nHARN_BIN=\"$(command -v harn)\" harn test tests --parallel --timing\nharn run scripts/regen_demo.harn\nHARN_BIN=\"$(command -v harn)\" harn run scripts/package_install_smoke.harn\nharn run scripts/check_fixture_staleness.harn\n```\n\nWhen using the sibling upstream checkout instead:\n\n```sh\nOPENAPI_ROOT=\"$(pwd)\"\ncd ../harn\ncargo run --quiet --bin harn -- check \"$OPENAPI_ROOT/src\" \"$OPENAPI_ROOT/scripts\"\ncargo run --quiet --bin harn -- lint \"$OPENAPI_ROOT/src\" \"$OPENAPI_ROOT/scripts\"\ncargo run --quiet --bin harn -- fmt --check \"$OPENAPI_ROOT/src\" \"$OPENAPI_ROOT/scripts\" \"$OPENAPI_ROOT/tests\"\ncargo run --quiet --bin harn -- package check \"$OPENAPI_ROOT\"\nHARN_BIN=\"$PWD/target/debug/harn\" cargo run --quiet --bin harn -- test \"$OPENAPI_ROOT/tests\" --parallel --timing\nHARN_BIN=\"$PWD/target/debug/harn\" cargo run --quiet --bin harn -- run \"$OPENAPI_ROOT/scripts/regen_demo.harn\"\nHARN_BIN=\"$PWD/target/debug/harn\" cargo run --quiet --bin harn -- run \"$OPENAPI_ROOT/scripts/package_install_smoke.harn\"\ncargo run --quiet --bin harn -- run \"$OPENAPI_ROOT/scripts/check_fixture_staleness.harn\"\n```\n\n### CI and merge queue\n\nThe CI workflow runs on pull requests, `main` pushes, merge queue\n`merge_group` events, and manual dispatch. It installs the Harn version pinned\nby `.harn-version`: first from the published Linux release archive, then from\ncrates.io with `cargo install --locked` if the archive is unavailable.\n\nBranch rules require the aggregate `CI status` job. Keep every required check\nas a dependency of that job so repository rulesets and merge queue\nconfiguration do not drift when individual job names change.\n\n`main` is protected by GitHub's merge queue. Changes should be pushed on a\nbranch, opened as a PR, and queued after checks pass. The merge queue runs the\nsame workflows again on GitHub's synthetic queue branch before landing the PR\non `main`.\n\n### Fixture refresh workflow\n\nThe Notion OpenAPI fixture is intentionally pinned. To refresh it manually,\nsave the current fixture, run the refresh script, then review the diff report:\n\n```sh\ncp tests/fixtures/notion.openapi.json /tmp/notion.openapi.old.json\nharn run scripts/refresh_fixtures.harn\nharn run scripts/fixture_diff.harn -- \\\n  /tmp/notion.openapi.old.json \\\n  tests/fixtures/notion.openapi.json\n```\n\n`scripts/refresh_fixtures.harn` writes both\n`tests/fixtures/notion.openapi.json` and\n`tests/fixtures/notion.openapi.json.meta.toml`, including the upstream URL,\ncapture timestamp, byte size, and SHA-256. CI runs\n`scripts/check_fixture_staleness.harn`; it verifies the committed byte size and\nSHA-256 before checking age, is quiet under 90 days old, warns\nbetween 90 and 180 days, and fails non-`main` branches once the fixture is over\n180 days old.\n\n### Harn CLI version bumps\n\nGitHub Actions reads the Harn version from `.harn-version`. When a new Harn\nrelease is published, update that pin and run the local gate with:\n\n```sh\nharn run scripts/bump_harn_cli_version.harn -- 0.8.80\n```\n\nThe script accepts a leading `v` (`v0.8.80` is normalized to `0.8.80`),\ninstalls that `harn-cli` release into a temp directory, then runs check, lint,\nformatting, the smoke test suite, `scripts/regen_demo.harn`, and\n`scripts/package_install_smoke.harn`. Use `--no-verify` only when you\nintentionally want to edit the version pin without running the gate.\n\n## License\n\nDual-licensed under MIT and Apache-2.0. Choose whichever you prefer.\n\n- [LICENSE-MIT](./LICENSE-MIT)\n- [LICENSE-APACHE](./LICENSE-APACHE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fburin-labs%2Fharn-openapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fburin-labs%2Fharn-openapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fburin-labs%2Fharn-openapi/lists"}