diff --git a/flake.nix b/flake.nix
index 1d54eac26..193cd3875 100644
--- a/flake.nix
+++ b/flake.nix
@@ -355,6 +355,7 @@
               "nix-store-tests" = { };
 
               "nix-fetchers" = { };
+              "nix-fetchers-c" = { };
               "nix-fetchers-tests" = { };
 
               "nix-expr" = { };
@@ -363,6 +364,7 @@
               "nix-expr-tests" = { };
 
               "nix-flake" = { };
+              "nix-flake-c" = { };
               "nix-flake-tests" = { };
 
               "nix-main" = { };
diff --git a/meson.build b/meson.build
index 49adf9832..621946608 100644
--- a/meson.build
+++ b/meson.build
@@ -33,6 +33,7 @@ endif
 # External C wrapper libraries
 subproject('libutil-c')
 subproject('libstore-c')
+subproject('libfetchers-c')
 subproject('libexpr-c')
 subproject('libflake-c')
 subproject('libmain-c')
diff --git a/packaging/components.nix b/packaging/components.nix
index ea5d97399..6351ac712 100644
--- a/packaging/components.nix
+++ b/packaging/components.nix
@@ -333,6 +333,7 @@ in
   nix-store-tests = callPackage ../src/libstore-tests/package.nix { };
 
   nix-fetchers = callPackage ../src/libfetchers/package.nix { };
+  nix-fetchers-c = callPackage ../src/libfetchers-c/package.nix { };
   nix-fetchers-tests = callPackage ../src/libfetchers-tests/package.nix { };
 
   nix-expr = callPackage ../src/libexpr/package.nix { };
diff --git a/packaging/everything.nix b/packaging/everything.nix
index 1835eefb6..5bf57f95a 100644
--- a/packaging/everything.nix
+++ b/packaging/everything.nix
@@ -15,6 +15,7 @@
   nix-store-tests,
 
   nix-fetchers,
+  nix-fetchers-c,
   nix-fetchers-tests,
 
   nix-expr,
@@ -54,6 +55,7 @@ let
         nix-store
         nix-store-c
         nix-fetchers
+        nix-fetchers-c
         nix-expr
         nix-expr-c
         nix-flake
@@ -230,6 +232,7 @@ stdenv.mkDerivation (finalAttrs: {
       "nix-expr"
       "nix-expr-c"
       "nix-fetchers"
+      "nix-fetchers-c"
       "nix-flake"
       "nix-flake-c"
       "nix-main"
diff --git a/packaging/hydra.nix b/packaging/hydra.nix
index 74e245f26..ee4cabe62 100644
--- a/packaging/hydra.nix
+++ b/packaging/hydra.nix
@@ -48,6 +48,7 @@ let
         "nix-store-test-support"
         "nix-store-tests"
         "nix-fetchers"
+        "nix-fetchers-c"
         "nix-fetchers-tests"
         "nix-expr"
         "nix-expr-c"
diff --git a/src/libexpr-c/meson.build b/src/libexpr-c/meson.build
index 7c11ca9cb..ed4582e40 100644
--- a/src/libexpr-c/meson.build
+++ b/src/libexpr-c/meson.build
@@ -37,13 +37,11 @@ include_dirs = [include_directories('.')]
 
 headers = files(
   'nix_api_expr.h',
+  'nix_api_expr_internal.h',
   'nix_api_external.h',
   'nix_api_value.h',
 )
 
-# TODO move this header to libexpr, maybe don't use it in tests?
-headers += files('nix_api_expr_internal.h')
-
 subdir('nix-meson-build-support/export-all-symbols')
 subdir('nix-meson-build-support/windows-version')
 
diff --git a/src/libexpr-c/nix_api_expr.h b/src/libexpr-c/nix_api_expr.h
index f8d181452..2be739955 100644
--- a/src/libexpr-c/nix_api_expr.h
+++ b/src/libexpr-c/nix_api_expr.h
@@ -286,6 +286,11 @@ nix_err nix_gc_incref(nix_c_context * context, const void * object);
 /**
  * @brief Decrement the garbage collector reference counter for the given object
  *
+ * We also provide typed `nix_*_decref` functions, which are
+ *   - safer to use
+ *   - easier to integrate when deriving bindings
+ *   - allow more flexibility
+ *
  * @param[out] context Optional, stores error information
  * @param[in] object The object to stop referencing
  */
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index 51652db1f..4c0667d9e 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -10,6 +10,7 @@
 
 namespace nix {
 using json = nlohmann::json;
+// TODO: rename. It doesn't print.
 json printValueAsJSON(EvalState & state, bool strict,
     Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
 {
diff --git a/src/libfetchers-c/.version b/src/libfetchers-c/.version
new file mode 120000
index 000000000..b7badcd0c
--- /dev/null
+++ b/src/libfetchers-c/.version
@@ -0,0 +1 @@
+../../.version
\ No newline at end of file
diff --git a/src/libfetchers-c/meson.build b/src/libfetchers-c/meson.build
new file mode 100644
index 000000000..e34997f09
--- /dev/null
+++ b/src/libfetchers-c/meson.build
@@ -0,0 +1,65 @@
+project('nix-fetchers-c', 'cpp',
+  version : files('.version'),
+  default_options : [
+    'cpp_std=c++2a',
+    # TODO(Qyriad): increase the warning level
+    'warning_level=1',
+    'errorlogs=true', # Please print logs for tests that fail
+  ],
+  meson_version : '>= 1.1',
+  license : 'LGPL-2.1-or-later',
+)
+
+cxx = meson.get_compiler('cpp')
+
+subdir('nix-meson-build-support/deps-lists')
+
+deps_private_maybe_subproject = [
+  dependency('nix-util'),
+  dependency('nix-store'),
+  dependency('nix-fetchers'),
+]
+deps_public_maybe_subproject = [
+  dependency('nix-util-c'),
+  dependency('nix-store-c'),
+]
+subdir('nix-meson-build-support/subprojects')
+
+add_project_arguments(
+  language : 'cpp',
+)
+
+subdir('nix-meson-build-support/common')
+
+sources = files(
+  'nix_api_fetchers.cc',
+)
+
+include_dirs = [include_directories('.')]
+
+headers = files(
+  'nix_api_fetchers.h',
+  'nix_api_fetchers_internal.hh',
+)
+
+# TODO move this header to libexpr, maybe don't use it in tests?
+headers += files('nix_api_fetchers.h')
+
+subdir('nix-meson-build-support/export-all-symbols')
+subdir('nix-meson-build-support/windows-version')
+
+this_library = library(
+  'nixfetchersc',
+  sources,
+  dependencies : deps_public + deps_private + deps_other,
+  include_directories : include_dirs,
+  link_args: linker_export_flags,
+  prelink : true, # For C++ static initializers
+  install : true,
+)
+
+install_headers(headers, preserve_path : true)
+
+libraries_private = []
+
+subdir('nix-meson-build-support/export')
diff --git a/src/libfetchers-c/nix-meson-build-support b/src/libfetchers-c/nix-meson-build-support
new file mode 120000
index 000000000..0b140f56b
--- /dev/null
+++ b/src/libfetchers-c/nix-meson-build-support
@@ -0,0 +1 @@
+../../nix-meson-build-support
\ No newline at end of file
diff --git a/src/libfetchers-c/nix_api_fetchers.cc b/src/libfetchers-c/nix_api_fetchers.cc
new file mode 100644
index 000000000..4e8037a5e
--- /dev/null
+++ b/src/libfetchers-c/nix_api_fetchers.cc
@@ -0,0 +1,19 @@
+#include "nix_api_fetchers.h"
+#include "nix_api_fetchers_internal.hh"
+#include "nix_api_util_internal.h"
+
+nix_fetchers_settings * nix_fetchers_settings_new(nix_c_context * context)
+{
+    try {
+        auto fetchersSettings = nix::make_ref<nix::fetchers::Settings>(nix::fetchers::Settings{});
+        return new nix_fetchers_settings{
+            .settings = fetchersSettings,
+        };
+    }
+    NIXC_CATCH_ERRS_NULL
+}
+
+void nix_fetchers_settings_free(nix_fetchers_settings * settings)
+{
+    delete settings;
+}
diff --git a/src/libfetchers-c/nix_api_fetchers.h b/src/libfetchers-c/nix_api_fetchers.h
new file mode 100644
index 000000000..19da112a6
--- /dev/null
+++ b/src/libfetchers-c/nix_api_fetchers.h
@@ -0,0 +1,32 @@
+#ifndef NIX_API_FETCHERS_H
+#define NIX_API_FETCHERS_H
+/** @defgroup libfetchers libfetchers
+ * @brief Bindings to the Nix fetchers library
+ * @{
+ */
+/** @file
+ * @brief Main entry for the libfetchers C bindings
+ */
+
+#include "nix_api_util.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+// cffi start
+
+// Type definitions
+/**
+ * @brief Shared settings object
+ */
+typedef struct nix_fetchers_settings nix_fetchers_settings;
+
+nix_fetchers_settings * nix_fetchers_settings_new(nix_c_context * context);
+
+void nix_fetchers_settings_free(nix_fetchers_settings * settings);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // NIX_API_FETCHERS_H
\ No newline at end of file
diff --git a/src/libfetchers-c/nix_api_fetchers_internal.hh b/src/libfetchers-c/nix_api_fetchers_internal.hh
new file mode 100644
index 000000000..b0dea5754
--- /dev/null
+++ b/src/libfetchers-c/nix_api_fetchers_internal.hh
@@ -0,0 +1,12 @@
+#pragma once
+#include "nix/fetchers/fetch-settings.hh"
+#include "nix/util/ref.hh"
+
+/**
+ * A shared reference to `nix::fetchers::Settings`
+ * @see nix::fetchers::Settings
+ */
+struct nix_fetchers_settings
+{
+    nix::ref<nix::fetchers::Settings> settings;
+};
diff --git a/src/libfetchers-c/package.nix b/src/libfetchers-c/package.nix
new file mode 100644
index 000000000..9a601d704
--- /dev/null
+++ b/src/libfetchers-c/package.nix
@@ -0,0 +1,50 @@
+{
+  lib,
+  mkMesonLibrary,
+
+  nix-store-c,
+  nix-expr-c,
+  nix-util-c,
+  nix-fetchers,
+
+  # Configuration Options
+
+  version,
+}:
+
+let
+  inherit (lib) fileset;
+in
+
+mkMesonLibrary (finalAttrs: {
+  pname = "nix-fetchers-c";
+  inherit version;
+
+  workDir = ./.;
+  fileset = fileset.unions [
+    ../../nix-meson-build-support
+    ./nix-meson-build-support
+    ../../.version
+    ./.version
+    ./meson.build
+    # ./meson.options
+    (fileset.fileFilter (file: file.hasExt "cc") ./.)
+    (fileset.fileFilter (file: file.hasExt "hh") ./.)
+    (fileset.fileFilter (file: file.hasExt "h") ./.)
+  ];
+
+  propagatedBuildInputs = [
+    nix-util-c
+    nix-expr-c
+    nix-store-c
+    nix-fetchers
+  ];
+
+  mesonFlags = [
+  ];
+
+  meta = {
+    platforms = lib.platforms.unix ++ lib.platforms.windows;
+  };
+
+})
diff --git a/src/libfetchers-tests/meson.build b/src/libfetchers-tests/meson.build
index 12b748e65..33bc7f30e 100644
--- a/src/libfetchers-tests/meson.build
+++ b/src/libfetchers-tests/meson.build
@@ -17,6 +17,7 @@ subdir('nix-meson-build-support/deps-lists')
 deps_private_maybe_subproject = [
   dependency('nix-store-test-support'),
   dependency('nix-fetchers'),
+  dependency('nix-fetchers-c'),
 ]
 deps_public_maybe_subproject = [
 ]
@@ -39,6 +40,7 @@ subdir('nix-meson-build-support/common')
 sources = files(
   'access-tokens.cc',
   'git-utils.cc',
+  'nix_api_fetchers.cc',
   'public-key.cc',
 )
 
diff --git a/src/libfetchers-tests/nix_api_fetchers.cc b/src/libfetchers-tests/nix_api_fetchers.cc
new file mode 100644
index 000000000..8f3e6e3c5
--- /dev/null
+++ b/src/libfetchers-tests/nix_api_fetchers.cc
@@ -0,0 +1,18 @@
+#include "gmock/gmock.h"
+#include <gtest/gtest.h>
+
+#include "nix_api_fetchers.h"
+#include "nix/store/tests/nix_api_store.hh"
+
+namespace nixC {
+
+TEST_F(nix_api_store_test, nix_api_fetchers_new_free)
+{
+    nix_fetchers_settings * settings = nix_fetchers_settings_new(ctx);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, settings);
+
+    nix_fetchers_settings_free(settings);
+}
+
+} // namespace nixC
diff --git a/src/libfetchers-tests/package.nix b/src/libfetchers-tests/package.nix
index 6e3581183..48c1a07d8 100644
--- a/src/libfetchers-tests/package.nix
+++ b/src/libfetchers-tests/package.nix
@@ -5,6 +5,7 @@
   mkMesonExecutable,
 
   nix-fetchers,
+  nix-fetchers-c,
   nix-store-test-support,
 
   libgit2,
@@ -40,6 +41,7 @@ mkMesonExecutable (finalAttrs: {
 
   buildInputs = [
     nix-fetchers
+    nix-fetchers-c
     nix-store-test-support
     rapidcheck
     gtest
diff --git a/src/libflake-c/meson.build b/src/libflake-c/meson.build
index fd3cdd01b..5a81618c8 100644
--- a/src/libflake-c/meson.build
+++ b/src/libflake-c/meson.build
@@ -17,12 +17,14 @@ subdir('nix-meson-build-support/deps-lists')
 deps_private_maybe_subproject = [
   dependency('nix-util'),
   dependency('nix-store'),
+  dependency('nix-fetchers'),
   dependency('nix-expr'),
   dependency('nix-flake'),
 ]
 deps_public_maybe_subproject = [
   dependency('nix-util-c'),
   dependency('nix-store-c'),
+  dependency('nix-fetchers-c'),
   dependency('nix-expr-c'),
 ]
 subdir('nix-meson-build-support/subprojects')
@@ -37,6 +39,7 @@ include_dirs = [include_directories('.')]
 
 headers = files(
   'nix_api_flake.h',
+  'nix_api_flake_internal.hh',
 )
 
 # TODO move this header to libexpr, maybe don't use it in tests?
diff --git a/src/libflake-c/nix_api_flake.cc b/src/libflake-c/nix_api_flake.cc
index a1b586e82..06b139f91 100644
--- a/src/libflake-c/nix_api_flake.cc
+++ b/src/libflake-c/nix_api_flake.cc
@@ -1,12 +1,16 @@
 #include "nix_api_flake.h"
 #include "nix_api_flake_internal.hh"
+#include "nix_api_util.h"
 #include "nix_api_util_internal.h"
 #include "nix_api_expr_internal.h"
+#include "nix_api_fetchers_internal.hh"
+#include "nix_api_fetchers.h"
 
 #include "nix/flake/flake.hh"
 
 nix_flake_settings * nix_flake_settings_new(nix_c_context * context)
 {
+    nix_clear_err(context);
     try {
         auto settings = nix::make_ref<nix::flake::Settings>();
         return new nix_flake_settings{settings};
@@ -22,8 +26,177 @@ void nix_flake_settings_free(nix_flake_settings * settings)
 nix_err nix_flake_settings_add_to_eval_state_builder(
     nix_c_context * context, nix_flake_settings * settings, nix_eval_state_builder * builder)
 {
+    nix_clear_err(context);
     try {
         settings->settings->configureEvalSettings(builder->settings);
     }
     NIXC_CATCH_ERRS
 }
+
+nix_flake_reference_parse_flags *
+nix_flake_reference_parse_flags_new(nix_c_context * context, nix_flake_settings * settings)
+{
+    nix_clear_err(context);
+    try {
+        return new nix_flake_reference_parse_flags{
+            .baseDirectory = std::nullopt,
+        };
+    }
+    NIXC_CATCH_ERRS_NULL
+}
+
+void nix_flake_reference_parse_flags_free(nix_flake_reference_parse_flags * flags)
+{
+    delete flags;
+}
+
+nix_err nix_flake_reference_parse_flags_set_base_directory(
+    nix_c_context * context,
+    nix_flake_reference_parse_flags * flags,
+    const char * baseDirectory,
+    size_t baseDirectoryLen)
+{
+    nix_clear_err(context);
+    try {
+        flags->baseDirectory.emplace(nix::Path{std::string(baseDirectory, baseDirectoryLen)});
+        return NIX_OK;
+    }
+    NIXC_CATCH_ERRS
+}
+
+nix_err nix_flake_reference_and_fragment_from_string(
+    nix_c_context * context,
+    nix_fetchers_settings * fetchSettings,
+    nix_flake_settings * flakeSettings,
+    nix_flake_reference_parse_flags * parseFlags,
+    const char * strData,
+    size_t strSize,
+    nix_flake_reference ** flakeReferenceOut,
+    nix_get_string_callback fragmentCallback,
+    void * fragmentCallbackUserData)
+{
+    nix_clear_err(context);
+    *flakeReferenceOut = nullptr;
+    try {
+        std::string str(strData, 0, strSize);
+
+        auto [flakeRef, fragment] =
+            nix::parseFlakeRefWithFragment(*fetchSettings->settings, str, parseFlags->baseDirectory, true);
+        *flakeReferenceOut = new nix_flake_reference{nix::make_ref<nix::FlakeRef>(flakeRef)};
+        return call_nix_get_string_callback(fragment, fragmentCallback, fragmentCallbackUserData);
+    }
+    NIXC_CATCH_ERRS
+}
+
+void nix_flake_reference_free(nix_flake_reference * flakeReference)
+{
+    delete flakeReference;
+}
+
+nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_flake_settings * settings)
+{
+    nix_clear_err(context);
+    try {
+        auto lockSettings = nix::make_ref<nix::flake::LockFlags>(nix::flake::LockFlags{
+            .recreateLockFile = false,
+            .updateLockFile = true,  // == `nix_flake_lock_flags_set_mode_write_as_needed`
+            .writeLockFile = true,   // == `nix_flake_lock_flags_set_mode_write_as_needed`
+            .failOnUnlocked = false, // == `nix_flake_lock_flags_set_mode_write_as_needed`
+            .useRegistries = false,
+            .allowUnlocked = false, // == `nix_flake_lock_flags_set_mode_write_as_needed`
+            .commitLockFile = false,
+
+        });
+        return new nix_flake_lock_flags{lockSettings};
+    }
+    NIXC_CATCH_ERRS_NULL
+}
+
+void nix_flake_lock_flags_free(nix_flake_lock_flags * flags)
+{
+    delete flags;
+}
+
+nix_err nix_flake_lock_flags_set_mode_virtual(nix_c_context * context, nix_flake_lock_flags * flags)
+{
+    nix_clear_err(context);
+    try {
+        flags->lockFlags->updateLockFile = true;
+        flags->lockFlags->writeLockFile = false;
+        flags->lockFlags->failOnUnlocked = false;
+        flags->lockFlags->allowUnlocked = true;
+    }
+    NIXC_CATCH_ERRS
+}
+
+nix_err nix_flake_lock_flags_set_mode_write_as_needed(nix_c_context * context, nix_flake_lock_flags * flags)
+{
+    nix_clear_err(context);
+    try {
+        flags->lockFlags->updateLockFile = true;
+        flags->lockFlags->writeLockFile = true;
+        flags->lockFlags->failOnUnlocked = false;
+        flags->lockFlags->allowUnlocked = true;
+    }
+    NIXC_CATCH_ERRS
+}
+
+nix_err nix_flake_lock_flags_set_mode_check(nix_c_context * context, nix_flake_lock_flags * flags)
+{
+    nix_clear_err(context);
+    try {
+        flags->lockFlags->updateLockFile = false;
+        flags->lockFlags->writeLockFile = false;
+        flags->lockFlags->failOnUnlocked = true;
+        flags->lockFlags->allowUnlocked = false;
+    }
+    NIXC_CATCH_ERRS
+}
+
+nix_err nix_flake_lock_flags_add_input_override(
+    nix_c_context * context, nix_flake_lock_flags * flags, const char * inputPath, nix_flake_reference * flakeRef)
+{
+    nix_clear_err(context);
+    try {
+        auto path = nix::flake::parseInputAttrPath(inputPath);
+        flags->lockFlags->inputOverrides.emplace(path, *flakeRef->flakeRef);
+        if (flags->lockFlags->writeLockFile) {
+            return nix_flake_lock_flags_set_mode_virtual(context, flags);
+        }
+    }
+    NIXC_CATCH_ERRS
+}
+
+nix_locked_flake * nix_flake_lock(
+    nix_c_context * context,
+    nix_fetchers_settings * fetchSettings,
+    nix_flake_settings * flakeSettings,
+    EvalState * eval_state,
+    nix_flake_lock_flags * flags,
+    nix_flake_reference * flakeReference)
+{
+    nix_clear_err(context);
+    try {
+        auto lockedFlake = nix::make_ref<nix::flake::LockedFlake>(nix::flake::lockFlake(
+            *flakeSettings->settings, eval_state->state, *flakeReference->flakeRef, *flags->lockFlags));
+        return new nix_locked_flake{lockedFlake};
+    }
+    NIXC_CATCH_ERRS_NULL
+}
+
+void nix_locked_flake_free(nix_locked_flake * lockedFlake)
+{
+    delete lockedFlake;
+}
+
+nix_value * nix_locked_flake_get_output_attrs(
+    nix_c_context * context, nix_flake_settings * settings, EvalState * evalState, nix_locked_flake * lockedFlake)
+{
+    nix_clear_err(context);
+    try {
+        auto v = nix_alloc_value(context, evalState);
+        nix::flake::callFlake(evalState->state, *lockedFlake->lockedFlake, v->value);
+        return v;
+    }
+    NIXC_CATCH_ERRS_NULL
+}
diff --git a/src/libflake-c/nix_api_flake.h b/src/libflake-c/nix_api_flake.h
index 75675835e..f5b9dc542 100644
--- a/src/libflake-c/nix_api_flake.h
+++ b/src/libflake-c/nix_api_flake.h
@@ -9,6 +9,7 @@
  * @brief Main entry for the libflake C bindings
  */
 
+#include "nix_api_fetchers.h"
 #include "nix_api_store.h"
 #include "nix_api_util.h"
 #include "nix_api_expr.h"
@@ -18,8 +19,46 @@ extern "C" {
 #endif
 // cffi start
 
+/**
+ * @brief A settings object for configuring the behavior of the nix-flake-c library.
+ * @see nix_flake_settings_new
+ * @see nix_flake_settings_free
+ */
 typedef struct nix_flake_settings nix_flake_settings;
 
+/**
+ * @brief Context and paramaters for parsing a flake reference
+ * @see nix_flake_reference_parse_flags_free
+ * @see nix_flake_reference_parse_string
+ */
+typedef struct nix_flake_reference_parse_flags nix_flake_reference_parse_flags;
+
+/**
+ * @brief A reference to a flake
+ *
+ * A flake reference specifies how to fetch a flake.
+ *
+ * @see nix_flake_reference_from_string
+ * @see nix_flake_reference_free
+ */
+typedef struct nix_flake_reference nix_flake_reference;
+
+/**
+ * @brief Parameters for locking a flake
+ * @see nix_flake_lock_flags_new
+ * @see nix_flake_lock_flags_free
+ * @see nix_flake_lock
+ */
+typedef struct nix_flake_lock_flags nix_flake_lock_flags;
+
+/**
+ * @brief A flake with a suitable lock (file or otherwise)
+ * @see nix_flake_lock
+ * @see nix_locked_flake_free
+ * @see nix_locked_flake_get_output_attrs
+ */
+typedef struct nix_locked_flake nix_locked_flake;
+
 // Function prototypes
 /**
  * Create a nix_flake_settings initialized with default values.
@@ -38,6 +77,8 @@ void nix_flake_settings_free(nix_flake_settings * settings);
  * @brief Initialize a `nix_flake_settings` to contain `builtins.getFlake` and
  * potentially more.
  *
+ * @warning This does not put the eval state in pure mode!
+ *
  * @param[out] context Optional, stores error information
  * @param[in] settings The settings to use for e.g. `builtins.getFlake`
  * @param[in] builder The builder to modify
@@ -45,6 +86,158 @@ void nix_flake_settings_free(nix_flake_settings * settings);
 nix_err nix_flake_settings_add_to_eval_state_builder(
     nix_c_context * context, nix_flake_settings * settings, nix_eval_state_builder * builder);
 
+/**
+ * @brief A new `nix_flake_reference_parse_flags` with defaults
+ */
+nix_flake_reference_parse_flags *
+nix_flake_reference_parse_flags_new(nix_c_context * context, nix_flake_settings * settings);
+
+/**
+ * @brief Deallocate and release the resources associated with a `nix_flake_reference_parse_flags`.
+ * Does not fail.
+ * @param[in] flags the `nix_flake_reference_parse_flags *` to free
+ */
+void nix_flake_reference_parse_flags_free(nix_flake_reference_parse_flags * flags);
+
+/**
+ * @brief Provide a base directory for parsing relative flake references
+ * @param[out] context Optional, stores error information
+ * @param[in] flags The flags to modify
+ * @param[in] baseDirectory The base directory to add
+ * @param[in] baseDirectoryLen The length of baseDirectory
+ * @return NIX_OK on success, NIX_ERR on failure
+ */
+nix_err nix_flake_reference_parse_flags_set_base_directory(
+    nix_c_context * context,
+    nix_flake_reference_parse_flags * flags,
+    const char * baseDirectory,
+    size_t baseDirectoryLen);
+
+/**
+ * @brief A new `nix_flake_lock_flags` with defaults
+ * @param[in] settings Flake settings that may affect the defaults
+ */
+nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_flake_settings * settings);
+
+/**
+ * @brief Deallocate and release the resources associated with a `nix_flake_lock_flags`.
+ * Does not fail.
+ * @param[in] settings the `nix_flake_lock_flags *` to free
+ */
+void nix_flake_lock_flags_free(nix_flake_lock_flags * settings);
+
+/**
+ * @brief Put the lock flags in a mode that checks whether the lock is up to date.
+ * @param[out] context Optional, stores error information
+ * @param[in] flags The flags to modify
+ * @return NIX_OK on success, NIX_ERR on failure
+ *
+ * This causes `nix_flake_lock` to fail if the lock needs to be updated.
+ */
+nix_err nix_flake_lock_flags_set_mode_check(nix_c_context * context, nix_flake_lock_flags * flags);
+
+/**
+ * @brief Put the lock flags in a mode that updates the lock file in memory, if needed.
+ * @param[out] context Optional, stores error information
+ * @param[in] flags The flags to modify
+ * @param[in] update Whether to allow updates
+ *
+ * This will cause `nix_flake_lock` to update the lock file in memory, if needed.
+ */
+nix_err nix_flake_lock_flags_set_mode_virtual(nix_c_context * context, nix_flake_lock_flags * flags);
+
+/**
+ * @brief Put the lock flags in a mode that updates the lock file on disk, if needed.
+ * @param[out] context Optional, stores error information
+ * @param[in] flags The flags to modify
+ * @param[in] update Whether to allow updates
+ *
+ * This will cause `nix_flake_lock` to update the lock file on disk, if needed.
+ */
+nix_err nix_flake_lock_flags_set_mode_write_as_needed(nix_c_context * context, nix_flake_lock_flags * flags);
+
+/**
+ * @brief Add input overrides to the lock flags
+ * @param[out] context Optional, stores error information
+ * @param[in] flags The flags to modify
+ * @param[in] inputPath The input path to override
+ * @param[in] flakeRef The flake reference to use as the override
+ *
+ * This switches the `flags` to `nix_flake_lock_flags_set_mode_virtual` if not in mode
+ * `nix_flake_lock_flags_set_mode_check`.
+ */
+nix_err nix_flake_lock_flags_add_input_override(
+    nix_c_context * context, nix_flake_lock_flags * flags, const char * inputPath, nix_flake_reference * flakeRef);
+
+/**
+ * @brief Lock a flake, if not already locked.
+ * @param[out] context Optional, stores error information
+ * @param[in] settings The flake (and fetch) settings to use
+ * @param[in] flags The locking flags to use
+ * @param[in] flake The flake to lock
+ */
+nix_locked_flake * nix_flake_lock(
+    nix_c_context * context,
+    nix_fetchers_settings * fetchSettings,
+    nix_flake_settings * settings,
+    EvalState * eval_state,
+    nix_flake_lock_flags * flags,
+    nix_flake_reference * flake);
+
+/**
+ * @brief Deallocate and release the resources associated with a `nix_locked_flake`.
+ * Does not fail.
+ * @param[in] locked_flake the `nix_locked_flake *` to free
+ */
+void nix_locked_flake_free(nix_locked_flake * locked_flake);
+
+/**
+ * @brief Parse a URL-like string into a `nix_flake_reference`.
+ *
+ * @param[out] context **context** – Optional, stores error information
+ * @param[in] fetchSettings **context** – The fetch settings to use
+ * @param[in] flakeSettings **context** – The flake settings to use
+ * @param[in] parseFlags **context** – Specific context and parameters such as base directory
+ *
+ * @param[in] str **input** – The URI-like string to parse
+ * @param[in] strLen **input** – The length of `str`
+ *
+ * @param[out] flakeReferenceOut **result** – The resulting flake reference
+ * @param[in] fragmentCallback **result** – A callback to call with the fragment part of the URL
+ * @param[in] fragmentCallbackUserData **result** – User data to pass to the fragment callback
+ *
+ * @return NIX_OK on success, NIX_ERR on failure
+ */
+nix_err nix_flake_reference_and_fragment_from_string(
+    nix_c_context * context,
+    nix_fetchers_settings * fetchSettings,
+    nix_flake_settings * flakeSettings,
+    nix_flake_reference_parse_flags * parseFlags,
+    const char * str,
+    size_t strLen,
+    nix_flake_reference ** flakeReferenceOut,
+    nix_get_string_callback fragmentCallback,
+    void * fragmentCallbackUserData);
+
+/**
+ * @brief Deallocate and release the resources associated with a `nix_flake_reference`.
+ *
+ * Does not fail.
+ *
+ * @param[in] store the `nix_flake_reference *` to free
+ */
+void nix_flake_reference_free(nix_flake_reference * store);
+
+/**
+ * @brief Get the output attributes of a flake.
+ * @param[out] context Optional, stores error information
+ * @param[in] settings The settings to use
+ * @param[in] locked_flake the flake to get the output attributes from
+ * @return A new nix_value or NULL on failure. Release the `nix_value` with `nix_value_decref`.
+ */
+nix_value * nix_locked_flake_get_output_attrs(
+    nix_c_context * context, nix_flake_settings * settings, EvalState * evalState, nix_locked_flake * lockedFlake);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/src/libflake-c/nix_api_flake_internal.hh b/src/libflake-c/nix_api_flake_internal.hh
index f7c5e7838..fbc6574d6 100644
--- a/src/libflake-c/nix_api_flake_internal.hh
+++ b/src/libflake-c/nix_api_flake_internal.hh
@@ -1,9 +1,32 @@
 #pragma once
+#include <optional>
 
 #include "nix/util/ref.hh"
+#include "nix/flake/flake.hh"
+#include "nix/flake/flakeref.hh"
 #include "nix/flake/settings.hh"
 
 struct nix_flake_settings
 {
     nix::ref<nix::flake::Settings> settings;
 };
+
+struct nix_flake_reference_parse_flags
+{
+    std::optional<nix::Path> baseDirectory;
+};
+
+struct nix_flake_reference
+{
+    nix::ref<nix::FlakeRef> flakeRef;
+};
+
+struct nix_flake_lock_flags
+{
+    nix::ref<nix::flake::LockFlags> lockFlags;
+};
+
+struct nix_locked_flake
+{
+    nix::ref<nix::flake::LockedFlake> lockedFlake;
+};
diff --git a/src/libflake-c/package.nix b/src/libflake-c/package.nix
index 114950852..8c6883d9c 100644
--- a/src/libflake-c/package.nix
+++ b/src/libflake-c/package.nix
@@ -4,6 +4,7 @@
 
   nix-store-c,
   nix-expr-c,
+  nix-fetchers-c,
   nix-flake,
 
   # Configuration Options
@@ -35,6 +36,7 @@ mkMesonLibrary (finalAttrs: {
   propagatedBuildInputs = [
     nix-expr-c
     nix-store-c
+    nix-fetchers-c
     nix-flake
   ];
 
diff --git a/src/libflake-tests/nix_api_flake.cc b/src/libflake-tests/nix_api_flake.cc
index 455fcb15d..f7e0cb719 100644
--- a/src/libflake-tests/nix_api_flake.cc
+++ b/src/libflake-tests/nix_api_flake.cc
@@ -1,7 +1,6 @@
+#include "nix/util/file-system.hh"
 #include "nix_api_store.h"
-#include "nix_api_store_internal.h"
 #include "nix_api_util.h"
-#include "nix_api_util_internal.h"
 #include "nix_api_expr.h"
 #include "nix_api_value.h"
 #include "nix_api_flake.h"
@@ -14,7 +13,7 @@
 
 namespace nixC {
 
-TEST_F(nix_api_store_test, nix_api_init_global_getFlake_exists)
+TEST_F(nix_api_store_test, nix_api_init_getFlake_exists)
 {
     nix_libstore_init(ctx);
     assert_ctx_ok();
@@ -51,4 +50,334 @@ TEST_F(nix_api_store_test, nix_api_init_global_getFlake_exists)
     ASSERT_EQ(NIX_TYPE_FUNCTION, nix_get_type(ctx, value));
 }
 
+TEST_F(nix_api_store_test, nix_api_flake_reference_not_absolute_no_basedir_fail)
+{
+    nix_libstore_init(ctx);
+    assert_ctx_ok();
+    nix_libexpr_init(ctx);
+    assert_ctx_ok();
+
+    auto settings = nix_flake_settings_new(ctx);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, settings);
+
+    auto fetchSettings = nix_fetchers_settings_new(ctx);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, fetchSettings);
+
+    auto parseFlags = nix_flake_reference_parse_flags_new(ctx, settings);
+
+    std::string str(".#legacyPackages.aarch127-unknown...orion");
+    std::string fragment;
+    nix_flake_reference * flakeReference = nullptr;
+    auto r = nix_flake_reference_and_fragment_from_string(
+        ctx, fetchSettings, settings, parseFlags, str.data(), str.size(), &flakeReference, OBSERVE_STRING(fragment));
+
+    ASSERT_NE(NIX_OK, r);
+    ASSERT_EQ(nullptr, flakeReference);
+
+    nix_flake_reference_parse_flags_free(parseFlags);
+}
+
+TEST_F(nix_api_store_test, nix_api_load_flake)
+{
+    auto tmpDir = nix::createTempDir();
+    nix::AutoDelete delTmpDir(tmpDir, true);
+
+    nix::writeFile(tmpDir + "/flake.nix", R"(
+        {
+            outputs = { ... }: {
+                hello = "potato";
+            };
+        }
+    )");
+
+    nix_libstore_init(ctx);
+    assert_ctx_ok();
+    nix_libexpr_init(ctx);
+    assert_ctx_ok();
+
+    auto fetchSettings = nix_fetchers_settings_new(ctx);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, fetchSettings);
+
+    auto settings = nix_flake_settings_new(ctx);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, settings);
+
+    nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store);
+    ASSERT_NE(nullptr, builder);
+    assert_ctx_ok();
+
+    auto state = nix_eval_state_build(ctx, builder);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, state);
+
+    nix_eval_state_builder_free(builder);
+
+    auto parseFlags = nix_flake_reference_parse_flags_new(ctx, settings);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, parseFlags);
+
+    auto r0 = nix_flake_reference_parse_flags_set_base_directory(ctx, parseFlags, tmpDir.c_str(), tmpDir.size());
+    assert_ctx_ok();
+    ASSERT_EQ(NIX_OK, r0);
+
+    std::string fragment;
+    const std::string ref = ".#legacyPackages.aarch127-unknown...orion";
+    nix_flake_reference * flakeReference = nullptr;
+    auto r = nix_flake_reference_and_fragment_from_string(
+        ctx, fetchSettings, settings, parseFlags, ref.data(), ref.size(), &flakeReference, OBSERVE_STRING(fragment));
+    assert_ctx_ok();
+    ASSERT_EQ(NIX_OK, r);
+    ASSERT_NE(nullptr, flakeReference);
+    ASSERT_EQ(fragment, "legacyPackages.aarch127-unknown...orion");
+
+    nix_flake_reference_parse_flags_free(parseFlags);
+
+    auto lockFlags = nix_flake_lock_flags_new(ctx, settings);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, lockFlags);
+
+    auto lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, lockedFlake);
+
+    nix_flake_lock_flags_free(lockFlags);
+
+    auto value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, value);
+
+    auto helloAttr = nix_get_attr_byname(ctx, value, state, "hello");
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, helloAttr);
+
+    std::string helloStr;
+    nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr));
+    assert_ctx_ok();
+    ASSERT_EQ("potato", helloStr);
+
+    nix_value_decref(ctx, value);
+    nix_locked_flake_free(lockedFlake);
+    nix_flake_reference_free(flakeReference);
+    nix_state_free(state);
+    nix_flake_settings_free(settings);
+}
+
+TEST_F(nix_api_store_test, nix_api_load_flake_with_flags)
+{
+    nix_libstore_init(ctx);
+    assert_ctx_ok();
+    nix_libexpr_init(ctx);
+    assert_ctx_ok();
+
+    auto tmpDir = nix::createTempDir();
+    nix::AutoDelete delTmpDir(tmpDir, true);
+
+    nix::createDirs(tmpDir + "/b");
+    nix::writeFile(tmpDir + "/b/flake.nix", R"(
+        {
+            outputs = { ... }: {
+                hello = "BOB";
+            };
+        }
+    )");
+
+    nix::createDirs(tmpDir + "/a");
+    nix::writeFile(tmpDir + "/a/flake.nix", R"(
+        {
+            inputs.b.url = ")" + tmpDir + R"(/b";
+            outputs = { b, ... }: {
+                hello = b.hello;
+            };
+        }
+    )");
+
+    nix::createDirs(tmpDir + "/c");
+    nix::writeFile(tmpDir + "/c/flake.nix", R"(
+        {
+            outputs = { ... }: {
+                hello = "Claire";
+            };
+        }
+    )");
+
+    nix_libstore_init(ctx);
+    assert_ctx_ok();
+
+    auto fetchSettings = nix_fetchers_settings_new(ctx);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, fetchSettings);
+
+    auto settings = nix_flake_settings_new(ctx);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, settings);
+
+    nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store);
+    ASSERT_NE(nullptr, builder);
+    assert_ctx_ok();
+
+    auto state = nix_eval_state_build(ctx, builder);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, state);
+
+    nix_eval_state_builder_free(builder);
+
+    auto parseFlags = nix_flake_reference_parse_flags_new(ctx, settings);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, parseFlags);
+
+    auto r0 = nix_flake_reference_parse_flags_set_base_directory(ctx, parseFlags, tmpDir.c_str(), tmpDir.size());
+    assert_ctx_ok();
+    ASSERT_EQ(NIX_OK, r0);
+
+    std::string fragment;
+    const std::string ref = "./a";
+    nix_flake_reference * flakeReference = nullptr;
+    auto r = nix_flake_reference_and_fragment_from_string(
+        ctx, fetchSettings, settings, parseFlags, ref.data(), ref.size(), &flakeReference, OBSERVE_STRING(fragment));
+    assert_ctx_ok();
+    ASSERT_EQ(NIX_OK, r);
+    ASSERT_NE(nullptr, flakeReference);
+    ASSERT_EQ(fragment, "");
+
+    // Step 1: Do not update, fails
+
+    auto lockFlags = nix_flake_lock_flags_new(ctx, settings);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, lockFlags);
+
+    nix_flake_lock_flags_set_mode_check(ctx, lockFlags);
+    assert_ctx_ok();
+
+    // Step 2: Update but do not write, succeeds
+
+    auto lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference);
+    assert_ctx_err();
+    ASSERT_EQ(nullptr, lockedFlake);
+
+    nix_flake_lock_flags_set_mode_virtual(ctx, lockFlags);
+    assert_ctx_ok();
+
+    lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, lockedFlake);
+
+    // Get the output attrs
+    auto value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, value);
+
+    auto helloAttr = nix_get_attr_byname(ctx, value, state, "hello");
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, helloAttr);
+
+    std::string helloStr;
+    nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr));
+    assert_ctx_ok();
+    ASSERT_EQ("BOB", helloStr);
+
+    nix_value_decref(ctx, value);
+    nix_locked_flake_free(lockedFlake);
+
+    // Step 3: Lock was not written, so Step 1 would fail again
+
+    nix_flake_lock_flags_set_mode_check(ctx, lockFlags);
+
+    lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference);
+    assert_ctx_err();
+    ASSERT_EQ(nullptr, lockedFlake);
+
+    // Step 4: Update and write, succeeds
+
+    nix_flake_lock_flags_set_mode_write_as_needed(ctx, lockFlags);
+    assert_ctx_ok();
+
+    lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, lockedFlake);
+
+    // Get the output attrs
+    value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, value);
+
+    helloAttr = nix_get_attr_byname(ctx, value, state, "hello");
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, helloAttr);
+
+    helloStr.clear();
+    nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr));
+    assert_ctx_ok();
+    ASSERT_EQ("BOB", helloStr);
+
+    nix_value_decref(ctx, value);
+    nix_locked_flake_free(lockedFlake);
+
+    // Step 5: Lock was written, so Step 1 would succeed
+
+    nix_flake_lock_flags_set_mode_check(ctx, lockFlags);
+    assert_ctx_ok();
+
+    lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, lockedFlake);
+
+    // Get the output attrs
+    value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, value);
+
+    helloAttr = nix_get_attr_byname(ctx, value, state, "hello");
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, helloAttr);
+
+    helloStr.clear();
+    nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr));
+    assert_ctx_ok();
+    ASSERT_EQ("BOB", helloStr);
+
+    nix_value_decref(ctx, value);
+    nix_locked_flake_free(lockedFlake);
+
+    // Step 6: Lock with override, do not write
+
+    nix_flake_lock_flags_set_mode_write_as_needed(ctx, lockFlags);
+    assert_ctx_ok();
+
+    nix_flake_reference * overrideFlakeReference = nullptr;
+    nix_flake_reference_and_fragment_from_string(
+        ctx, fetchSettings, settings, parseFlags, "./c", 3, &overrideFlakeReference, OBSERVE_STRING(fragment));
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, overrideFlakeReference);
+
+    nix_flake_lock_flags_add_input_override(ctx, lockFlags, "b", overrideFlakeReference);
+    assert_ctx_ok();
+
+    lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, lockedFlake);
+
+    // Get the output attrs
+    value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake);
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, value);
+
+    helloAttr = nix_get_attr_byname(ctx, value, state, "hello");
+    assert_ctx_ok();
+    ASSERT_NE(nullptr, helloAttr);
+
+    helloStr.clear();
+    nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr));
+    assert_ctx_ok();
+    ASSERT_EQ("Claire", helloStr);
+
+    nix_flake_reference_parse_flags_free(parseFlags);
+    nix_flake_lock_flags_free(lockFlags);
+    nix_flake_reference_free(flakeReference);
+    nix_state_free(state);
+    nix_flake_settings_free(settings);
+}
+
 } // namespace nixC
diff --git a/src/libflake/include/nix/flake/flake.hh b/src/libflake/include/nix/flake/flake.hh
index 3336f8557..3b98f1400 100644
--- a/src/libflake/include/nix/flake/flake.hh
+++ b/src/libflake/include/nix/flake/flake.hh
@@ -63,7 +63,7 @@ struct ConfigFile
 };
 
 /**
- * The contents of a flake.nix file.
+ * A flake in context
  */
 struct Flake
 {
diff --git a/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh b/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh
index 006dc497c..382c7b292 100644
--- a/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh
+++ b/src/libutil-test-support/include/nix/util/tests/nix_api_util.hh
@@ -3,6 +3,7 @@
 #include "nix_api_util.h"
 
 #include <gtest/gtest.h>
+#include <string_view>
 
 namespace nixC {
 
@@ -24,7 +25,12 @@ protected:
 
     nix_c_context * ctx;
 
-    inline void assert_ctx_ok()
+    inline std::string loc(const char * file, int line)
+    {
+        return std::string(file) + ":" + std::to_string(line);
+    }
+
+    inline void assert_ctx_ok(const char * file, int line)
     {
         if (nix_err_code(ctx) == NIX_OK) {
             return;
@@ -32,16 +38,18 @@ protected:
         unsigned int n;
         const char * p = nix_err_msg(nullptr, ctx, &n);
         std::string msg(p, n);
-        throw std::runtime_error(std::string("nix_err_code(ctx) != NIX_OK, message: ") + msg);
+        throw std::runtime_error(loc(file, line) + ": nix_err_code(ctx) != NIX_OK, message: " + msg);
     }
+#define assert_ctx_ok() assert_ctx_ok(__FILE__, __LINE__)
 
-    inline void assert_ctx_err()
+    inline void assert_ctx_err(const char * file, int line)
     {
         if (nix_err_code(ctx) != NIX_OK) {
             return;
         }
-        throw std::runtime_error("Got NIX_OK, but expected an error!");
+        throw std::runtime_error(loc(file, line) + ": Got NIX_OK, but expected an error!");
     }
+#define assert_ctx_err() assert_ctx_err(__FILE__, __LINE__)
 };
 
 }