Note: this is kind of stream-of-consciousness, just getting ideas/thoughts out there.
I assume a modder could do something like this:
{
"custom_requirement": "radiant.mods.load_script('my_namespace.custom_requirements.this_particular_requirement'):eval(crafter)"
}
and then in a my_namespace/custom_requirements/this_particular_requirement.lua have code like this?
local CustomRequirement = class()
function CustomRequirement:eval(crafter)
local can_craft = false
-- get crafter's town to perform checks for wealth/buffs
-- combine multiple checks with ors/ands into can_craft
return can_craft
end
return CustomRequirement
Alternately, since white space can come in any form in Lua, you can replace all the line breaks in a function with spaces and put the whole thing in "custom_requirement", but it could be pretty ugly:
{
-- Lua psuedo-code:
"custom_requirement": "for each thing do if service:function(thing) then return true end end"
}
Another option would be to have separate files that contain all the functions, and these files are loaded up via a JSON in which people can mixinto their own function file references (so they don’t have to monkey-patch a single targeted Lua file themselves): then this mod would load up that JSON file at the beginning, identifying all the Lua files it needed to load up and index them by keys ('my_namespace:my_function' should be enough). Then you could do something like this:
{
"custom_requirement": "self:custom_functions['my_namespace:my_function'](crafter)"
}
But now that I think of it, this makes it difficult for other people to patch those functions; they would have to create new functions and override the "custom_requirement" setting everywhere that particular requirement was being used (imagine that they added a custom trait that was similar to “cultist” and they wanted all recipes that required a cultist to also allow crafting if that trait were present). I think taking advantage of the infrastructure provided to us by Radiant with the manifest system could help: having the modders who use this define aliases, functions, that sort of thing in their manifest, and then other modders in turn could monkey-patch or override those aliases/functions/files with their own.
But as @max99x would say, it depends on how permissive you want your API to be, and I tend to get overly zealous in giving people tools/access. 
Edit: An example for handling custom functions in an organized fashion would be to do something like the heatmap (client) service that I wrote for ACE, which is essentially what I described with the JSON index file, but that’s a working example you can look at (it includes initialization callback functions and should allow hot-loading heatmaps that aren’t included in the keys JSON, though I never tested that, and looking at it now I think there might be some issues with the combination of callback functions and hot-loading in particular).