diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for cozo-hs
+
+## 0.1.0.0 -- 2023-11-03
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+# cozo-hs
+
+### A library with bindings and simple types to the C API of the embeddable Cozo database.
+
+## Installation
+
+This library relies on pkg-config for libcozo_c being available on your system. You will need to have `cozo_c.h` and `libcozo_c.so` installed somewhere your linker can find it and it must be registered with pkg-config. 
+
+To do so, you can download both the header files as well as the library from the [Cozo project repository](https://github.com/cozodb/cozo) or you could clone that repository and build it from source. 
+
+If there is no official way to install this library on your OS/Distro (Debian for example) then you can put the library and headers in common paths (/usr/local/lib, /usr/local/include respectively) then manually create a pkg-config file for them. Just make sure that the version you specify matches what you have installed!
+
+Here is an example libcozo_c.pc:
+```
+Name: cozo_c
+Description: C Interface with the Cozo embeddable database.
+Version: 0.7.5
+Cflags: -I/usr/local/include
+Libs: -L/usr/local/lib -lcozo_c
+```
+
+After that, a simple `cabal build` can make sure that the C compiler is linking correctly.
+
+After building, you may want to ensure that the runtime is linked correctly as well. Run `cabal repl` to try out some of the code or `cabal test` to run the test-suite.
+
+Following all of this, you can include this library as a dependency in other Cabal projects the normal way.
+
+## Notes on Usage
+
+This library makes heavy use of aeson given that the C API is defined almost entirely in terms of JSON. Therefore, some primitive data types which match the structure of returned JSON are provided as part of this library.
+
+If you desire to use your own serialization scheme rather than the types provided, the underlying bindings are exposed in the form of functions with a `'` suffix. All of these functions deal with strict `ByteStrings` that are marshalled from the strings produced by C.
+
+All errors are handled in a pure Either.
+
+A very simple program which opens an in-memory connection and runs a query is as follows:
+```Haskell
+{-#LANGUAGE OverloadedStrings#-}
+import Database.Cozo
+import Control.Exception (bracket, throwIO)
+
+main :: IO ()
+main = 
+  bracket 
+    (open "mem" "" "{}" >>= either throwIO pure)
+    (fmap (const ()) . close)
+    $ \c -> do
+      er <- runQuery c "?[] <- [[1,2,3]]" KM.empty
+      case er of
+        Left e -> throwIO e
+        Right (CozoResult badOkay) -> 
+          case badOkay of
+            Left bad -> print . cozoBadMessage $ bad
+            Right okay -> print . namedRowsRows . cozoOkayNamedRows $ okay
+```
+
+This program will output:
+
+```
+[[Number 1.0,Number 2.0,Number 3.0]]
+```
diff --git a/cozo-hs.cabal b/cozo-hs.cabal
new file mode 100644
--- /dev/null
+++ b/cozo-hs.cabal
@@ -0,0 +1,55 @@
+cabal-version:      3.0
+name:               cozo-hs
+version:            0.1.0.0
+synopsis:           Haskell bindings to the CozoDB C API
+description:        Simple, relatively low-level bindings to the C API
+                    provided by cozo. Additionally, there are some convenience types for
+                    serializing function arguments and deserializing query results.
+license:            MPL-2.0
+license-file:       LICENSE
+author:             Henry Johnson
+maintainer:         hencutJohnson@gmail.com
+-- copyright:
+category:           Database
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+                    README.md
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:
+        Database.Cozo
+    other-modules:
+        Database.Cozo.Internal
+        Database.Cozo.Internal.Bindings
+    other-extensions:
+        CApiFFI
+        ForeignFunctionInterface
+        OverloadedStrings
+        StrictData
+    build-depends:
+        base ^>=4.18.0.0,
+        aeson >= 2.2.1 && < 2.3,
+        bytestring >= 0.11.4 && < 0.12,
+        text >= 2.0.2 && < 2.1,
+    hs-source-dirs:   src
+    default-language: GHC2021
+    pkgconfig-depends: libcozo_c == 0.7.5
+
+test-suite cozo-hs-test
+    import: warnings
+    build-depends:
+        base ^>= 4.18.0.0,
+        cozo-hs,
+        aeson >= 2.2.1 && < 2.3,
+        bytestring >= 0.11.4 && < 0.12,
+        text >= 2.0.2 && < 2.1,
+        tasty == 1.5,
+        tasty-hunit == 0.10.1
+    type: exitcode-stdio-1.0
+    main-is: Main.hs
+    hs-source-dirs: test
+    default-language: GHC2021
diff --git a/src/Database/Cozo.hs b/src/Database/Cozo.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Cozo.hs
@@ -0,0 +1,495 @@
+{-# HLINT ignore "Use const" #-}
+{-# HLINT ignore "Eta reduce" #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StrictData #-}
+{-# OPTIONS_GHC -Wno-typed-holes #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+
+{- |
+Module      : Database.Cozo
+Description : Wrappers and types for the Cozo C API
+License     : MPL-2.0
+Maintainer  : hencutJohnson@gmail.com
+
+Included are some wrapping functions for Cozo's C API and data types to deserialize them.
+-}
+module Database.Cozo (
+  -- * Data
+  CozoResult (..),
+  CozoOkay (..),
+  NamedRows (..),
+  CozoBad (..),
+  CozoRelationExportPayload (..),
+  CozoException (..),
+
+  -- * Functions
+  open,
+  close,
+  runQuery,
+  backup,
+  restore,
+  importRelations,
+  exportRelations,
+  importFromBackup,
+
+  -- ** Lower Level Wrappers
+  open',
+  close',
+  runQuery',
+  backup',
+  restore',
+  importRelations',
+  exportRelations',
+  importFromBackup',
+
+  -- * Re-exports
+  Connection,
+  CozoNullResultPtrException,
+  Database.Cozo.Internal.InternalCozoError,
+  Key,
+  KeyMap,
+  KM.empty,
+  KM.singleton,
+  KM.insert,
+  KM.fromList,
+  Value (..),
+) where
+
+import Control.Exception (Exception)
+import Data.Aeson (
+  FromJSON (parseJSON),
+  Options (fieldLabelModifier),
+  ToJSON (..),
+  Value (..),
+  defaultOptions,
+  eitherDecodeStrict,
+  fromEncoding,
+  genericParseJSON,
+  genericToEncoding,
+  genericToJSON,
+  withObject,
+  (.:),
+ )
+import Data.Aeson.KeyMap (Key, KeyMap)
+import Data.Aeson.KeyMap qualified as KM
+import Data.Aeson.Types (Encoding, Parser)
+import Data.Bifunctor (Bifunctor (bimap, first))
+import Data.ByteString (ByteString, toStrict)
+import Data.ByteString.Builder (toLazyByteString)
+import Data.Char (toLower)
+import Data.Text (Text)
+import Data.Text.Encoding (encodeUtf8)
+import Database.Cozo.Internal (
+  Connection,
+  CozoNullResultPtrException,
+  InternalCozoError,
+  backup',
+  close',
+  exportRelations',
+  importFromBackup',
+  importRelations',
+  open',
+  restore',
+  runQuery',
+ )
+import GHC.Generics (Generic)
+
+{- |
+Relation information with headers, their values, and another `NamedRows` if
+it exists.
+-}
+data NamedRows = NamedRows
+  { namedRowsHeaders :: [Text]
+  , namedRowsRows :: [[Value]]
+  , namedRowsNext :: Maybe NamedRows
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON NamedRows where
+  parseJSON :: Value -> Parser NamedRows
+  parseJSON =
+    genericParseJSON
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 9 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+
+instance ToJSON NamedRows where
+  toJSON :: NamedRows -> Value
+  toJSON =
+    genericToJSON
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 9 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+  toEncoding :: NamedRows -> Encoding
+  toEncoding =
+    genericToEncoding
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 9 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+
+data ConstJSON = ConstJSON deriving (Show, Eq, Generic)
+
+instance FromJSON ConstJSON where
+  parseJSON :: Value -> Parser ConstJSON
+  parseJSON _ = pure ConstJSON
+
+{- |
+A failure that cannot be recovered from easily.
+-}
+data CozoException
+  = -- | An internal error may occur when a connection is first being established
+    -- but not after that.
+    CozoExceptionInternal InternalCozoError
+  | -- | If any operation in the underlying C API returns a null pointer instead
+    -- of a pointer to a valid string, this error will be returned.
+    CozoErrorNullPtr CozoNullResultPtrException
+  | -- | The result of any operation fails to be deserialized appropriately.
+    -- This is a problem with the wrapper for the API and should be
+    -- submitted as an issue if it ever arises.
+    CozoJSONParseException String
+  | -- | A non-query operation such as a backup or import failed.
+    -- These usually occur because the user is trying to import or export a
+    -- relation that does not exist in the target database.
+    CozoOperationFailed Text
+  deriving (Show, Eq, Generic)
+
+instance Exception CozoException
+
+newtype CozoMessage = CozoMessage {runCozoMessage :: Text}
+  deriving (Show, Eq, Generic)
+
+instance FromJSON CozoMessage where
+  parseJSON :: Value -> Parser CozoMessage
+  parseJSON =
+    genericParseJSON
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 7 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+
+cozoMessageToException :: CozoMessage -> CozoException
+cozoMessageToException (CozoMessage m) = CozoOperationFailed m
+
+{- |
+A map of names and the relations they contain.
+This type is intended to be used as input to an import function
+or otherwise stored as JSON.
+-}
+newtype CozoRelationExportPayload = CozoRelationExportPayload
+  { cozoRelationExportPayloadData :: KeyMap NamedRows
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON CozoRelationExportPayload where
+  parseJSON :: Value -> Parser CozoRelationExportPayload
+  parseJSON =
+    genericParseJSON
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 25 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+
+{- |
+An intermediate type for decoding structures that return an object with a 'message' field
+when the 'ok' field is false.
+-}
+newtype IntermediateCozoMessageOnNotOK a = IntermediateCozoMessageOnNotOK
+  { runIntermediateCozoMessageOnNotOK :: Either CozoMessage a
+  }
+  deriving (Show, Eq, Generic)
+
+instance (FromJSON a) => FromJSON (IntermediateCozoMessageOnNotOK a) where
+  parseJSON :: Value -> Parser (IntermediateCozoMessageOnNotOK a)
+  parseJSON =
+    eitherOkay
+      "IntermediateCozoRelationExport"
+      (fmap (IntermediateCozoMessageOnNotOK . Left) . parseJSON)
+      (fmap (IntermediateCozoMessageOnNotOK . Right) . parseJSON)
+
+data IntermediateCozoImportFromRelationInput = IntermediateCozoImportFromRelationInput
+  { intermediateCozoImportFromRelationInputPath :: Text
+  , intermediateCozoImportFromRelationInputRelations :: [Text]
+  }
+  deriving (Show, Eq, Generic)
+
+instance ToJSON IntermediateCozoImportFromRelationInput where
+  toJSON :: IntermediateCozoImportFromRelationInput -> Value
+  toJSON =
+    genericToJSON
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 39 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+  toEncoding :: IntermediateCozoImportFromRelationInput -> Encoding
+  toEncoding =
+    genericToEncoding
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 39 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+
+{- |
+An intermediate type for packing a list of named relations into an object with the form
+\"{'relations': [...]}\"
+-}
+newtype IntermediateCozoRelationInput = IntermediateCozoRelationInput
+  { intermediateCozoRelationInputRelations :: [Text]
+  }
+  deriving (Show, Eq, Generic)
+
+instance ToJSON IntermediateCozoRelationInput where
+  toJSON :: IntermediateCozoRelationInput -> Value
+  toJSON =
+    genericToJSON
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 29 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+  toEncoding ::
+    IntermediateCozoRelationInput ->
+    Encoding
+  toEncoding =
+    genericToEncoding
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 29 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+
+{- |
+An okay result from a query.
+
+Contains result headers, and rows among other things.
+-}
+data CozoOkay = CozoOkay
+  { cozoOkayNamedRows :: NamedRows
+  , cozoOkayTook :: Double
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON CozoOkay where
+  parseJSON :: Value -> Parser CozoOkay
+  parseJSON =
+    withObject "CozoOkay" $ \o ->
+      CozoOkay
+        <$> parseJSON (Object o)
+        <*> o
+        .: "took"
+
+{- |
+A bad result from a query.
+
+Contains information on what went wrong.
+-}
+data CozoBad = CozoBad
+  { cozoBadDisplay :: Maybe Text
+  , cozoBadMessage :: Text
+  , cozoBadSeverity :: Maybe Text
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON CozoBad where
+  parseJSON :: Value -> Parser CozoBad
+  parseJSON =
+    genericParseJSON
+      ( defaultOptions
+          { fieldLabelModifier = \s ->
+              case drop 7 s of
+                [] -> []
+                x : xs -> toLower x : xs
+          }
+      )
+
+newtype CozoResult = CozoResult {runCozoResult :: Either CozoBad CozoOkay}
+  deriving (Show, Eq, Generic)
+
+instance FromJSON CozoResult where
+  parseJSON :: Value -> Parser CozoResult
+  parseJSON =
+    eitherOkay
+      "CozoResult"
+      (fmap (CozoResult . Left) . parseJSON)
+      (fmap (CozoResult . Right) . parseJSON)
+
+{- |
+Open a connection to a cozo database
+
+- engine: \"mem\", \"sqlite\" or \"rocksdb\"
+- path: utf8 encoded filepath
+- options: engine-specific options. \"{}\" is an acceptable empty value.
+-}
+open :: Text -> Text -> Text -> IO (Either CozoException Connection)
+open engine path options =
+  first CozoExceptionInternal
+    <$> open'
+      (encodeUtf8 engine)
+      (encodeUtf8 path)
+      (encodeUtf8 options)
+
+{- |
+True if the database was closed and False if it was already closed or if it
+does not exist.
+-}
+close :: Connection -> IO Bool
+close = close'
+
+{- |
+Run a utf8 encoded query with a map of parameters.
+
+Parameters are declared with
+text names and can be any valid JSON type. They are referenced in a query by a \"$\"
+preceding their name.
+-}
+runQuery ::
+  Connection ->
+  Text ->
+  KeyMap Value ->
+  IO (Either CozoException CozoResult)
+runQuery c query params = do
+  r <-
+    runQuery'
+      c
+      (encodeUtf8 query)
+      ( toStrict
+          . toLazyByteString
+          . fromEncoding
+          . toEncoding
+          $ params
+      )
+  pure $ first CozoErrorNullPtr r >>= cozoDecode
+
+{- |
+Backup a database.
+
+Accepts the path of the output file.
+-}
+backup :: Connection -> Text -> IO (Either CozoException ())
+backup c path =
+  decodeCozoCharPtrFn
+    <$> backup' c (encodeUtf8 path)
+
+{- |
+Restore a database from a backup.
+-}
+restore :: Connection -> Text -> IO (Either CozoException ())
+restore c path =
+  decodeCozoCharPtrFn
+    <$> restore' c (encodeUtf8 path)
+
+{- |
+Import data in relations.
+
+Triggers are not run for relations, if you wish to activate triggers, use a query
+  with parameters.
+-}
+importRelations ::
+  Connection ->
+  CozoRelationExportPayload ->
+  IO (Either CozoException ())
+importRelations c (CozoRelationExportPayload km) =
+  decodeCozoCharPtrFn
+    <$> importRelations' c (strictToEncoding km)
+
+{- |
+Export the relations specified by the given names.
+-}
+exportRelations ::
+  Connection ->
+  [Text] ->
+  IO (Either CozoException CozoRelationExportPayload)
+exportRelations c bs = do
+  r <-
+    exportRelations'
+      c
+      ( strictToEncoding
+          . IntermediateCozoRelationInput
+          $ bs
+      )
+  pure
+    $ first CozoErrorNullPtr r
+    >>= first CozoJSONParseException
+    . eitherDecodeStrict @(IntermediateCozoMessageOnNotOK CozoRelationExportPayload)
+    >>= first cozoMessageToException
+    . runIntermediateCozoMessageOnNotOK
+
+{- |
+Import the relations corresponding to the given names
+from the specified path.
+-}
+importFromBackup :: Connection -> Text -> [Text] -> IO (Either CozoException ())
+importFromBackup c path relations =
+  decodeCozoCharPtrFn
+    <$> importFromBackup'
+      c
+      (strictToEncoding $ IntermediateCozoImportFromRelationInput path relations)
+
+decodeCozoCharPtrFn ::
+  Either CozoNullResultPtrException ByteString ->
+  Either CozoException ()
+decodeCozoCharPtrFn e =
+  first CozoErrorNullPtr e
+    >>= cozoDecode @(IntermediateCozoMessageOnNotOK ConstJSON)
+    >>= bimap cozoMessageToException (const ())
+    . runIntermediateCozoMessageOnNotOK
+
+cozoDecode :: (FromJSON a) => ByteString -> Either CozoException a
+cozoDecode = first CozoJSONParseException . eitherDecodeStrict
+
+{- |
+Helper for defining JSON disjunctions that
+switch on the value of an \"ok\" boolean field.
+-}
+eitherOkay ::
+  String ->
+  (Value -> Parser a) ->
+  (Value -> Parser a) ->
+  Value ->
+  Parser a
+eitherOkay s l r =
+  withObject
+    s
+    ( \o ->
+        case KM.lookup "ok" o of
+          Nothing -> fail "Result did not contain \"ok\" field"
+          Just ok ->
+            case ok of
+              Bool b ->
+                if b then r (Object o) else l (Object o)
+              _ -> fail "\"ok\" field did not contain a Boolean."
+    )
+
+strictToEncoding :: (ToJSON a) => a -> ByteString
+strictToEncoding =
+  toStrict
+    . toLazyByteString
+    . fromEncoding
+    . toEncoding
diff --git a/src/Database/Cozo/Internal.hs b/src/Database/Cozo/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Cozo/Internal.hs
@@ -0,0 +1,198 @@
+{-# LANGUAGE StrictData #-}
+{-# OPTIONS_GHC -Wno-typed-holes #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+{-# OPTIONS_HADDOCK hide #-}
+
+{-# HLINT ignore "Use newtype instead of data" #-}
+
+module Database.Cozo.Internal (
+  open',
+  close',
+  runQuery',
+  importRelations',
+  exportRelations',
+  backup',
+  restore',
+  importFromBackup',
+  Connection,
+  InternalCozoError (..),
+  CozoNullResultPtrException (..),
+) where
+
+import Control.Exception (Exception)
+import Control.Monad ((<=<))
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Coerce (coerce)
+import Database.Cozo.Internal.Bindings (
+  cozoBackup,
+  cozoCloseDB,
+  cozoExportRelations,
+  cozoFreeStr,
+  cozoImportFromBackup,
+  cozoImportRelations,
+  cozoOpenDB,
+  cozoRestore,
+  cozoRunQuery,
+ )
+import Foreign (
+  Ptr,
+  Storable (peek),
+  fromBool,
+  maybePeek,
+  new,
+  toBool,
+ )
+import Foreign.C.Types (CChar, CInt)
+import GHC.Generics (Generic)
+
+newtype Connection = Connection (Ptr CInt)
+
+{- |
+Wrapper around primitive failure states.
+-}
+newtype InternalCozoError
+  = InternalCozoError ByteString
+  deriving (Show, Eq, Generic)
+
+instance Exception InternalCozoError
+
+data CozoNullResultPtrException = CozoNullResultPtrException deriving (Show, Eq, Generic)
+
+instance Exception CozoNullResultPtrException
+
+{- |
+Open a connection to a cozo database
+
+- engine: \"mem\", \"sqlite\" or \"rocksdb\"
+- path: utf8 encoded filepath
+- options: engine-specific options. \"{}\" is an acceptable empty value.
+-}
+open' ::
+  -- | engine: \"mem\", \"sqlite\" or \"rocksdb\"
+  ByteString ->
+  -- | path: utf8 encoded filepath
+  ByteString ->
+  -- | options: engine-specific options. \"{}\" is an acceptable emtpy value.
+  ByteString ->
+  IO (Either InternalCozoError Connection)
+open' engineBs pathBs optionBs =
+  B.useAsCString engineBs $ \engine ->
+    B.useAsCString pathBs $ \path ->
+      B.useAsCString optionBs $ \options -> do
+        intPtr <- new @CInt 0
+        openMessagePtr <- cozoOpenDB engine path options intPtr
+        !mOpenMessage <- maybePeek B.packCString openMessagePtr
+        maybe
+          (pure . Right . Connection $ intPtr)
+          (\errStr -> (Left . InternalCozoError $ errStr) <$ cozoFreeStr openMessagePtr)
+          mOpenMessage
+
+{- |
+True if the database was closed and False if it was already closed or if it
+does not exist.
+-}
+close' :: Connection -> IO Bool
+close' = fmap toBool . cozoCloseDB <=< peek . coerce
+
+{- |
+Run a query.
+
+The `CozoError` that might arise from this function is a `CozoNullResultPtr`.
+If there are any errors internal to cozo, those will be returned as part of the
+JSON string returned in the Right value. Simple returning a Right value does not
+mean the query was successful.
+
+- script: utf8 encoded script to execute
+- params_raw: a utf8 encoded, JSON formatted map of parameters for use in the script.
+-}
+runQuery' ::
+  Connection ->
+  ByteString ->
+  ByteString ->
+  IO (Either CozoNullResultPtrException ByteString)
+runQuery' c q p =
+  B.useAsCString q $ \q' ->
+    B.useAsCString p $ \p' ->
+      cozoCharPtrFn (\i -> cozoRunQuery i q' p' (fromBool False)) c
+
+{- |
+Import data in relations.
+
+Triggers are not run for relations, if you wish to activate triggers, use a query
+  with parameters.
+
+The given bytestring is a utf8, JSON formatted payload of relations.
+In the same form as that given by `exportRelations'`
+-}
+importRelations' ::
+  Connection ->
+  ByteString ->
+  IO (Either CozoNullResultPtrException ByteString)
+importRelations' c payloadBs =
+  B.useAsCString payloadBs $ \payload ->
+    cozoCharPtrFn (`cozoImportRelations` payload) c
+
+{- |
+Export relations into JSON
+
+The given bytestring must be a utf8 encoded JSON payload. See the manual for expected
+fields.
+-}
+exportRelations' ::
+  Connection ->
+  ByteString ->
+  IO (Either CozoNullResultPtrException ByteString)
+exportRelations' c payloadBs =
+  B.useAsCString payloadBs $ \payload ->
+    cozoCharPtrFn (`cozoExportRelations` payload) c
+
+{- |
+Backup a database.
+
+Accepts the path of the output file.
+-}
+backup' :: Connection -> ByteString -> IO (Either CozoNullResultPtrException ByteString)
+backup' c pathBs =
+  B.useAsCString pathBs $ \path ->
+    cozoCharPtrFn (`cozoBackup` path) c
+
+{- |
+Restore a database from a backup.
+-}
+restore' :: Connection -> ByteString -> IO (Either CozoNullResultPtrException ByteString)
+restore' c pathBs =
+  B.useAsCString pathBs $ \path ->
+    cozoCharPtrFn (`cozoRestore` path) c
+
+{- |
+Import relations from a backup.
+
+Note that triggers are not run for the relations.
+To run triggers, use a query with parameters.
+
+- payload: @"{'path': ..., 'relations': [...]}"@
+-}
+importFromBackup' ::
+  Connection ->
+  ByteString ->
+  IO (Either CozoNullResultPtrException ByteString)
+importFromBackup' c payloadBs =
+  B.useAsCString payloadBs $ \payload ->
+    cozoCharPtrFn (`cozoImportFromBackup` payload) c
+
+{- |
+Helper function for using cozo bindings that do an action and return a
+string that needs to be freed.
+-}
+cozoCharPtrFn ::
+  (CInt -> IO (Ptr CChar)) ->
+  Connection ->
+  IO (Either CozoNullResultPtrException ByteString)
+cozoCharPtrFn a (Connection intPtr) = do
+  dbId <- peek intPtr
+  rPtr <- a dbId
+  !mR <- maybePeek B.packCString rPtr
+  case mR of
+    Nothing -> pure . Left $ CozoNullResultPtrException
+    Just r -> Right r <$ cozoFreeStr rPtr
diff --git a/src/Database/Cozo/Internal/Bindings.hs b/src/Database/Cozo/Internal/Bindings.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Cozo/Internal/Bindings.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE CApiFFI #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# OPTIONS_HADDOCK hide #-}
+
+module Database.Cozo.Internal.Bindings (
+  cozoOpenDB,
+  cozoCloseDB,
+  cozoRunQuery,
+  cozoImportRelations,
+  cozoExportRelations,
+  cozoBackup,
+  cozoRestore,
+  cozoImportFromBackup,
+  cozoFreeStr,
+) where
+
+import Foreign.C.Types (CBool (..), CChar, CInt (..))
+import Foreign.Ptr (Ptr)
+
+foreign import capi "cozo_c.h cozo_open_db"
+  cozoOpenDB :: Ptr CChar -> Ptr CChar -> Ptr CChar -> Ptr CInt -> IO (Ptr CChar)
+
+foreign import capi "cozo_c.h cozo_close_db"
+  cozoCloseDB :: CInt -> IO CBool
+
+foreign import capi "cozo_c.h cozo_run_query"
+  cozoRunQuery :: CInt -> Ptr CChar -> Ptr CChar -> CBool -> IO (Ptr CChar)
+
+foreign import capi "cozo_c.h cozo_import_relations"
+  cozoImportRelations :: CInt -> Ptr CChar -> IO (Ptr CChar)
+
+foreign import capi "cozo_c.h cozo_export_relations"
+  cozoExportRelations :: CInt -> Ptr CChar -> IO (Ptr CChar)
+
+foreign import capi "cozo_c.h cozo_backup"
+  cozoBackup :: CInt -> Ptr CChar -> IO (Ptr CChar)
+
+foreign import capi "cozo_c.h cozo_restore"
+  cozoRestore :: CInt -> Ptr CChar -> IO (Ptr CChar)
+
+foreign import capi "cozo_c.h cozo_import_from_backup"
+  cozoImportFromBackup :: CInt -> Ptr CChar -> IO (Ptr CChar)
+
+foreign import capi "cozo_c.h cozo_free_str"
+  cozoFreeStr :: Ptr CChar -> IO ()
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,96 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -Wno-typed-holes #-}
+
+import Control.Exception (bracket, throwIO)
+import Data.Aeson (toJSON)
+import Data.Either (isLeft)
+import Database.Cozo
+import Test.Tasty
+import Test.Tasty.HUnit
+
+main :: IO ()
+main =
+  defaultMain
+    . testGroup "cozo-hs Tests"
+    $ [ connectionTests
+      , withResource
+          (open "mem" "" "{}" >>= either throwIO pure)
+          ((() <$) . close)
+          runQueryTests
+      , exportAndImportTests
+      ]
+
+connectionTests :: (HasCallStack) => TestTree
+connectionTests =
+  testGroup
+    "Connection Tests"
+    [ testCase "Establish Connection"
+        $ open "mem" "" "{}"
+        >>= either throwIO pure
+        >>= close
+        >>= assertBool "Database was already closed or did not exist."
+    ]
+
+exportAndImportTests :: (HasCallStack) => TestTree
+exportAndImportTests =
+  testGroup
+    "Export and Import Tests"
+    [ testCase "Can export and import" $ do
+        e <-
+          testWithConnection
+            $ \c -> do
+              _ <-
+                runQuery
+                  c
+                  "?[foo] <- [['foo']]; \
+                  \:create Foo{foo:String}"
+                  empty
+                  >>= either throwIO pure
+              exportRelations c ["Foo"] >>= either throwIO pure
+        (CozoResult (Right o)) <- testWithConnection $ \c -> do
+          _ <- runQuery c ":create Foo{foo:String}" empty >>= either throwIO pure
+          _ <- importRelations c e
+          runQuery c "?[foo] := *Foo{foo}" empty >>= either throwIO pure
+
+        assertEqual
+          "Expecting a single relation to be exported."
+          [[String "foo"]]
+          . namedRowsRows
+          . cozoOkayNamedRows
+          $ o
+    ]
+
+runQueryTests :: (HasCallStack) => IO Connection -> TestTree
+runQueryTests ioc =
+  testGroup
+    "runQuery Tests"
+    [ testCase "Example Query" $ do
+        c <- ioc
+        rs <-
+          unsafeRows
+            =<< runQuery c "?[] <- [[1,2,3], ['a', 'b', 'c']]" empty
+        assertEqual
+          "Expecting a literal query to return valid results."
+          [toJSON @Int <$> [1, 2, 3], toJSON @String <$> ["a", "b", "c"]]
+          rs
+    , testCase "Parameterized Query" $ do
+        c <- ioc
+        rs <-
+          unsafeRows
+            =<< runQuery c "?[] <- [[$foo]]" (singleton "foo" (toJSON @String "foo"))
+        assertEqual "" [[String "foo"]] rs
+    , testCase "Can Capture Error Messages" $ do
+        c <- ioc
+        (Right (CozoResult cr)) <- runQuery c "?[] <- [[1,2,3]" empty
+        assertBool "Expecting a failure for incorrect query syntax" (isLeft cr)
+    ]
+
+unsafeRows :: Either a CozoResult -> IO [[Value]]
+unsafeRows (Right (CozoResult (Right (CozoOkay (NamedRows _ rs _) _)))) = pure rs
+unsafeRows _ = fail "Failed to extract rows."
+
+testWithConnection :: (Connection -> IO c) -> IO c
+testWithConnection =
+  bracket
+    (open "mem" "" "{}" >>= either throwIO pure)
+    ((() <$) . close)
