diff --git a/examples/.gitignore b/examples/.gitignore
index 2cacfa9..0ac007f 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -1,7 +1,4 @@
basic_in.ur
*.exe
*.sql
-slackSecrets.ur
-zoomSecrets.ur
-oidcSecrets.ur
-dropboxSecrets.ur
+*Secrets.ur
diff --git a/examples/zenefitsDemo.ur b/examples/zenefitsDemo.ur
new file mode 100644
index 0000000..4757168
--- /dev/null
+++ b/examples/zenefitsDemo.ur
@@ -0,0 +1,11 @@
+(* For this demo, it's necessary to create zenefitsScrets.ur,
+ * defining [api_token]. *)
+structure Z = Zenefits.Make(Zenefits.TwoLegged(ZenefitsSecrets))
+
+fun main () =
+ ps <- Z.People.list;
+ return
+
+ {List.mapX (fn p => - {[p.FirstName]} {[p.LastName]} ({[p.WorkEmail]})
) ps}
+
+
diff --git a/examples/zenefitsDemo.urp b/examples/zenefitsDemo.urp
new file mode 100644
index 0000000..cfed0cd
--- /dev/null
+++ b/examples/zenefitsDemo.urp
@@ -0,0 +1,10 @@
+library ../src/ur
+rewrite all ZenefitsDemo/*
+database dbname=zenefitsDemo
+sql zenefitsDemo.sql
+safeGetDefault
+allow url https://*
+prefix http://localhost:8080/
+
+zenefitsSecrets
+zenefitsDemo
diff --git a/examples/zenefitsDemo.urs b/examples/zenefitsDemo.urs
new file mode 100644
index 0000000..6ac44e0
--- /dev/null
+++ b/examples/zenefitsDemo.urs
@@ -0,0 +1 @@
+val main : unit -> transaction page
diff --git a/src/ur/lib.urp b/src/ur/lib.urp
index 6d1171a..288e931 100644
--- a/src/ur/lib.urp
+++ b/src/ur/lib.urp
@@ -23,3 +23,4 @@ salesforce
slack
zoom
openIdConnect
+zenefits
diff --git a/src/ur/zenefits.ur b/src/ur/zenefits.ur
new file mode 100644
index 0000000..1958f18
--- /dev/null
+++ b/src/ur/zenefits.ur
@@ -0,0 +1,70 @@
+open Json
+open Urls
+
+functor TwoLegged(M : sig
+ val api_token : string
+ end) = struct
+ open M
+
+ val token = return (Some api_token)
+end
+
+type person = {
+ FirstName : option string,
+ PreferredName : option string,
+ LastName : option string,
+ WorkEmail : option string,
+ PersonalEmail : option string
+}
+val _ : json person = json_record
+ {FirstName = "first_name",
+ PreferredName = "preferred_name",
+ LastName = "last_name",
+ WorkEmail = "work_email",
+ PersonalEmail = "personal_email"}
+
+type subresults a = {
+ Data : list a,
+ NextUrl : option string
+}
+fun json_subresults [a] (_ : json a) : json (subresults a) =
+ json_record {Data = "data", NextUrl = "next_url"}
+
+type results a = {
+ Data : subresults a
+}
+fun json_results [a] (_ : json a) : json (results a) =
+ json_record {Data = "data"}
+
+functor Make(M : sig
+ val token : transaction (option string)
+ end) = struct
+ val token =
+ tok <- M.token;
+ case tok of
+ Some tok => return tok
+ | None => error How odd: no Zenefits token!
+
+ val urlPrefix = "https://api.zenefits.com/"
+
+ fun api url =
+ tok <- token;
+ WorldFfi.get url (Some ("Bearer " ^ tok)) True
+
+ fun apiPaged' [a] (_ : json a) (url : url) : transaction (list a) =
+ s <- api url;
+ debug (show url ^ " -> " ^ s);
+ r <- return (fromJson s : results a);
+ case r.Data.NextUrl of
+ None => return r.Data.Data
+ | Some nu =>
+ r' <- apiPaged' (bless nu);
+ return (List.append r.Data.Data r')
+
+ fun apiPaged [a] (_ : json a) (url : string) : transaction (list a) =
+ apiPaged' (bless (urlPrefix ^ url))
+
+ structure People = struct
+ val list = apiPaged "core/people"
+ end
+end
diff --git a/src/ur/zenefits.urs b/src/ur/zenefits.urs
new file mode 100644
index 0000000..5ea027f
--- /dev/null
+++ b/src/ur/zenefits.urs
@@ -0,0 +1,25 @@
+functor TwoLegged(M : sig
+ val api_token : string
+ end) : sig
+ val token : transaction (option string)
+end
+
+(** * Person API records *)
+
+type person = {
+ FirstName : option string,
+ PreferredName : option string,
+ LastName : option string,
+ WorkEmail : option string,
+ PersonalEmail : option string
+}
+
+(** * Now for the actual methods.... *)
+
+functor Make(M : sig
+ val token : transaction (option string)
+ end) : sig
+ structure People : sig
+ val list : transaction (list person)
+ end
+end