ec.fdk

Featherweight Development Kit for entrecode APIs.

ec.fdk docs

npm i ec.fdk

There are 2 ways to use ec.fdk:

  • method chaining
  • act

Start by calling fdk with your environment (stage | live), then method chain your way to success:

import { fdk } from 'ec.fdk';

fdk('stage') // choose stage environment
.dm('<shortID>') // select datamanager via short id
.model('muffin') // select model muffin
.entryList() // load entry list
.then((list) => {
console.log(list);
});

See all functions in the Fdk reference.

The act function converts a single object param into a fetch request:

const muffins = await act({
action: 'entryList',
env: 'stage',
dmShortID: '<shortID>',
model: 'muffin',
});

More in the act reference.

The act function is good to be used with swr or react-query:

import { act } from 'ec.fdk';
import useSWR from 'swr';

export function useFdk(config) {
const key = config ? JSON.stringify(config) : null;
return useSWR([key], () => act(config));
}

Then use the hook:

const config = {
env: 'stage',
dmShortID: '<shortID>',
};

function App() {
const { data: entryList } = useFdk({
...config,
action: 'entryList',
model: 'muffin',
});
/* more stuff */
}

You can replace the built-in HTTP fetcher with a custom function via .set({ fetcher }). This is useful for testing and mock modes — intercept all ec.fdk requests without monkey-patching globalThis.fetch:

import { fdk } from 'ec.fdk';

const mockFetcher = async (url, config, options) => {
// return fixture data based on URL
if (url.includes('/api/myShortID/settings')) {
return {
count: 1, total: 1,
_embedded: { 'myShortID:settings': [{ subdomain: 'test' }] },
};
}
return { count: 0, total: 0, _embedded: {} };
};

const api = fdk('stage')
.token('my-token')
.set({ fetcher: mockFetcher });

// uses mockFetcher instead of real HTTP — works with the full chaining API
const list = await api.dm('myShortID').model('settings').entryList();

The custom fetcher has the same signature as the internal fetcher: (url: string, config?: { token?: string; rawRes?: boolean }, options?: RequestInit) => Promise<any>. It must return parsed JSON (not a Response object) in the same shape as the entrecode API.

The Fetcher type is exported for convenience:

import type { Fetcher } from 'ec.fdk';

ec.fdk also ships a CLI for quick shell-based interaction with entrecode APIs. Output is JSON, so you can pipe it into jq and friends.

npx ec.fdk <command> [options]

Or install globally:

npm i -g ec.fdk
ec.fdk <command> [options]
Command Description Required flags
Entries --dm = shortID, --model required
entryList List entries --dm, --model
getEntry Get a single entry --dm, --model, --id
createEntry Create an entry --dm, --model, --data
editEntry Edit an entry --dm, --model, --id, --data
deleteEntry Delete an entry --dm, --model, --id
getSchema Get model schema --dm, --model
Datamanagers --id = DM UUID
dmList List datamanagers
getDatamanager Get a datamanager --id
createDatamanager Create a datamanager --data
editDatamanager Edit a datamanager (full PUT) --id, --data
deleteDatamanager Delete a datamanager --id
getStats Get datamanager stats
Models --id = DM UUID
modelList List models --id
createModel Create a model --id, --data
editModel Edit a model --id, --rid, --data
deleteModel Delete a model --id, --rid
Templates
createTemplate Create a template --data
Asset Groups --id = DM UUID
createAssetGroup Create an asset group --id, --data
editAssetGroup Edit an asset group --id, --rid, --data
Assets --dm = shortID
assetList List assets --dm, --assetgroup
getAsset Get a single asset --dm, --assetgroup, --rid
createAsset Upload asset(s) from local file --dm, --assetgroup, --file (repeatable)
editAsset Edit asset metadata --dm, --assetgroup, --rid, --data
deleteAsset Delete an asset --dm, --assetgroup, --rid
DM Clients --id = DM UUID
editDmClient Edit a DM client --id, --rid, --data
Roles --id = DM UUID
createRole Create a role --id, --data
editRole Edit a role --id, --rid, --data
deleteRole Delete a role --id, --rid
DM Accounts --id = DM UUID
editDmAccount Edit a DM account --id, --account-id, --data
deleteDmAccount Delete a DM account --id, --account-id
Account Clients
createAccountClient Create an account client --data
editAccountClient Edit an account client --rid, --data
deleteAccountClient Delete an account client --rid
Groups
createGroup Create a group --data
editGroup Edit a group --rid, --data
deleteGroup Delete a group --rid
Invites
createInvite Create an invite --data
editInvite Edit an invite --rid, --data
deleteInvite Delete an invite --rid
Accounts
editAccount Edit an account --account-id, --data
Tokens
listTokens List tokens --account-id
createToken Create a token --account-id
deleteToken Delete a token --account-id, --rid
Generic Resources
resourceList List any resource type --resource, -f
resourceGet Get a single resource --resource, -f
resourceEdit Edit a single resource --resource, -f, --data
resourceDelete Delete a single resource --resource, -f
Other
login Login via browser (OIDC). Use --password for email/password.
logout Logout and remove stored token
whoami Show current logged-in user
describe Show type definition for a command's return value <command>
typegen Generate typed entry APIs (.d.ts) --dm
getHistory Get dm-history entries -f shortID=<shortID>
install-skill Install Claude Code skill
update Self-update ec.fdk

resourceList resource types:

--resource --subdomain Typical filters
dm-account -f dataManagerID=<dataManagerID>
dm-client -f dataManagerID=<dataManagerID>
model -f dataManagerID=<dataManagerID>
assetgroup -f dataManagerID=<dataManagerID>
role -f dataManagerID=<dataManagerID>
tag -f dataManagerID=<dataManagerID>
template
account accounts
client accounts
group accounts
invite accounts
account/tokens accounts -f accountID=<accountID>
Option Description
-e, --env <env> Environment: stage | live (default: stage)
-d, --dm <shortID> DataManager short ID
-m, --model <name> Model name
-i, --id <id> Entry ID or DataManager UUID (context-dependent)
--rid <id> Resource ID (model, template, role, client, asset group, invite, etc.)
--account-id <id> Account ID
--assetgroup <name> Asset group name (for asset commands)
--file <path> Local file path for createAsset (repeatable)
--resource <name> Resource name (for resource* commands and describe)
--subdomain <name> Subdomain override (for resource* commands)
--data <json> JSON data (for create/edit, or pipe via stdin)
-s, --size <n> Page size for list
-p, --page <n> Page number for list
--sort <field> Sort field for list
-f, --filter <k=v> Filter for list (repeatable)
--password Use email/password login instead of browser OIDC
--dir <path> Target directory for install-skill (default: ~/.claude)
--raw Include _links and _embedded in output
--md Output entries as readable markdown table
--short Only print the return type, omit referenced types (for describe)
--models <a,b,c> Only generate types for these models (comma-separated, typegen only)
--out <path> Output file path for typegen (default: ./ec.fdk.generated.<shortID>.d.ts)
-v, --version Show version
-h, --help Show help

All examples use <PLACEHOLDER> values — replace them with your own resource IDs.

Placeholder Flag Resource
<shortID> --dm Datamanager
<dataManagerID> --id Datamanager
<entryID> --id Model
<modelID> --rid Model
<assetGroup> --rid / --assetgroup Asset Group
<assetID> --rid Asset
<clientID> --rid DM Client / Account Client
<roleID> --rid Role
<accountID> --account-id DM Account / Account
<groupID> --rid Group
<inviteID> --rid Invite
<tokenID> --rid Token
<collectionID> --data Template
# Login to stage via browser OIDC (stores token in ~/.ec-fdk/auth.json)
ec.fdk login -e stage

# Login via email/password prompt
ec.fdk login -e stage --password

# Logout
ec.fdk logout -e stage

# Show current user
ec.fdk whoami -e stage

# Install Claude Code skill (to ~/.claude/skills/ec-fdk/)
ec.fdk install-skill

# Install to custom directory
ec.fdk install-skill --dir ~/entrecode/.claude

# Self-update ec.fdk
ec.fdk update

# List datamanagers
ec.fdk dmList -e stage

# Get a single datamanager
ec.fdk getDatamanager -e stage --id <dataManagerID>

# List models of a datamanager
ec.fdk modelList -e stage --id <dataManagerID>

# Filter models by title
ec.fdk modelList --id <dataManagerID> -f title~=muffin

# List entries
ec.fdk entryList -d <shortID> -m muffin

# List with pagination
ec.fdk entryList -d <shortID> -m muffin -s 10 -p 2

# Get a single entry
ec.fdk getEntry -d <shortID> -m muffin -i <entryID>

# Create an entry
ec.fdk createEntry -d <shortID> -m muffin --data '{"name":"new muffin","amazement_factor":10}'

# Create via stdin pipe
echo '{"name":"piped muffin","amazement_factor":10}' | ec.fdk createEntry -d <shortID> -m muffin

# Edit an entry
ec.fdk editEntry -d <shortID> -m muffin -i <entryID> --data '{"name":"edited"}'

# Delete an entry
ec.fdk deleteEntry -d <shortID> -m muffin -i <entryID>

# Get model schema
ec.fdk getSchema -d <shortID> -m muffin

# Filter entries (repeatable -f for arbitrary query params)
ec.fdk entryList -d <shortID> -m muffin -f name~=chocolate
ec.fdk entryList -d <shortID> -m muffin -f amazement_factorFrom=5 -f amazement_factorTo=10
ec.fdk entryList -d <shortID> -m muffin -f createdFrom=2024-01-01

# Load multiple entries by ID (comma-separated)
ec.fdk entryList -d <shortID> -m muffin -f "id=abc123,def456,ghi789"

# Filter datamanagers
ec.fdk dmList -f title~=myproject

# Pipe into jq
ec.fdk entryList -d <shortID> -m muffin | jq '.items | length'
# Datamanager stats
ec.fdk getStats

# dm-history (requires shortID filter)
ec.fdk getHistory -f shortID=<shortID> -s 10

# List any resource type
ec.fdk resourceList --resource template -s 5
ec.fdk resourceList --resource client --subdomain accounts

# Generic get / edit / delete (works with any resource type)
ec.fdk resourceGet --resource model -f dataManagerID=<dataManagerID> -f modelID=<modelID>
ec.fdk resourceGet --resource group --subdomain accounts -f groupID=<groupID>
ec.fdk resourceEdit --resource invite --subdomain accounts -f invite=<inviteID> --data '{"email":"user@example.com","permissions":["dm-read"],"groups":[]}'
ec.fdk resourceDelete --resource role -f dataManagerID=<dataManagerID> -f roleID=<roleID>

# Datamanager
ec.fdk createDatamanager --data '{"title":"My DM","config":{}}'
ec.fdk deleteDatamanager --id <dataManagerID>

# editDatamanager is a full PUT — pass the complete resource, not just changed fields.
# Use getDatamanager + jq to build the payload:
ec.fdk getDatamanager --id <dataManagerID> \
| jq '.title = "New Title"' \
| ec.fdk editDatamanager --id <dataManagerID>

# Model
ec.fdk createModel --id <dataManagerID> --data '{"title":"article","locales":[],"fields":[]}'
ec.fdk editModel --id <dataManagerID> --rid <modelID> --data '{"title":"renamed","locales":[],"fields":[]}'
ec.fdk deleteModel --id <dataManagerID> --rid <modelID>

# Template
ec.fdk createTemplate --data '{"name":"My Template","collection":{"id":"<collectionID>","name":"my-collection","order":[],"requests":[]}}'

# Asset group
ec.fdk createAssetGroup --id <dataManagerID> --data '{"assetGroupID":"photos"}'
ec.fdk editAssetGroup --id <dataManagerID> --rid <assetGroup> --data '{"public":true}'

# Assets
ec.fdk assetList --dm <shortID> --assetgroup <assetGroup>
ec.fdk getAsset --dm <shortID> --assetgroup <assetGroup> --rid <assetID>
ec.fdk createAsset --dm <shortID> --assetgroup <assetGroup> --file ./photo.jpg
ec.fdk createAsset --dm <shortID> --assetgroup <assetGroup> --file ./a.jpg --file ./b.png
ec.fdk editAsset --dm <shortID> --assetgroup <assetGroup> --rid <assetID> --data '{"title":"sunset"}'
ec.fdk deleteAsset --dm <shortID> --assetgroup <assetGroup> --rid <assetID>

# DM client
ec.fdk editDmClient --id <dataManagerID> --rid <clientID> --data '{"callbackURL":"https://example.com/cb"}'

# Role
ec.fdk createRole --id <dataManagerID> --data '{"name":"editor"}'
ec.fdk editRole --id <dataManagerID> --rid <roleID> --data '{"name":"admin"}'
ec.fdk deleteRole --id <dataManagerID> --rid <roleID>

# DM account
ec.fdk editDmAccount --id <dataManagerID> --account-id <accountID> --data '{"email":"user@example.com"}'
ec.fdk deleteDmAccount --id <dataManagerID> --account-id <accountID>

# Account client
ec.fdk createAccountClient --data '{"clientID":"my-client","callbackURL":"https://example.com/cb"}'
# editAccountClient is a full PUT — clientID is required in the body.
ec.fdk resourceList --resource client --subdomain accounts -f clientID=my-client \
| jq '.items[0]' \
| jq '.callbackURL = "https://example.com/cb2"' \
| ec.fdk editAccountClient --rid my-client
ec.fdk deleteAccountClient --rid <clientID>

# Group
ec.fdk createGroup --data '{"name":"devs"}'
ec.fdk editGroup --rid <groupID> --data '{"name":"developers"}'
ec.fdk deleteGroup --rid <groupID>

# Invite
ec.fdk createInvite --data '{"email":"user@example.com"}'
# editInvite is a full PUT — pass the complete resource (email, permissions, groups).
ec.fdk editInvite --rid <inviteID> --data '{"email":"user@example.com","permissions":["dm-read"],"groups":[]}'
ec.fdk deleteInvite --rid <inviteID>

# Account
ec.fdk editAccount --account-id <accountID> --data '{"email":"user@example.com"}'

# Tokens
ec.fdk listTokens --account-id <accountID>
ec.fdk createToken --account-id <accountID>
ec.fdk deleteToken --account-id <accountID> --rid <tokenID>

Generate a .d.ts declaration file that gives you type-safe entry APIs with autocomplete for all models in a datamanager:

# Requires login first
ec.fdk login -e stage

# Generate types for all models (output: ./ec.fdk.generated.<shortID>.d.ts)
ec.fdk typegen --dm <shortID> --env stage

# Generate types for specific models only (faster, smaller output)
ec.fdk typegen --dm <shortID> --models site,settings,membership_config

# Custom output path
ec.fdk typegen --dm <shortID> --out ./types/my-dm.d.ts

The generated file includes a // Regenerate: comment with the exact command used, making it easy to re-run later.

Place the generated file in your TypeScript project. After that, model() carries the model name as a type parameter, so all entry methods return typed results:

import { fdk } from 'ec.fdk';

const muffin = await fdk('stage').dm('<shortID>').model('muffin').getEntry('abc');
muffin.name; // string — autocomplete works
muffin.amazement_factor; // number
muffin.bogus; // type error

await fdk('stage').dm('<shortID>').model('muffin').createEntry({
name: 'Blueberry', // required string
amazement_factor: 9, // required number
color: 'blue', // required string
});

await fdk('stage').dm('<shortID>').model('muffin').editEntry('abc', {
name: 'Updated', // partial — only changed fields needed
});

Without a generated file, everything falls back to the default EntryResource type with [key: string]: unknown — fully backwards-compatible.

Option Description
--dm <shortID> DataManager short ID (required)
--env <env> stage (default) or live
--models <a,b,c> Only generate types for these models (comma-separated)
--out <path> Output file path (default: ./ec.fdk.generated.<shortID>.d.ts)

JSON/object fields are generated as unknown since the schema has no structure info. You can refine these types by augmenting ModelOverrides in a separate file (so re-running typegen won't overwrite your changes):

// ec.fdk.overrides.d.ts
export {};
declare module "ec.fdk" {
interface ModelOverrides {
"restaurant": {
cuisine?: { italian: boolean; maxSeats: number } | null;
};
}
}

Only the fields you list are overridden — all other fields keep their generated types.

Show the return type of any command:

# Show the type definition for a command's return value (includes referenced types)
ec.fdk describe getAsset
ec.fdk describe entryList

# Only print the main return type, omit referenced types
ec.fdk describe getAsset --short

# List all describable commands
ec.fdk describe

For entry commands (getEntry, entryList, createEntry, editEntry), pass --dm and --model to generate a concrete type with actual field names and types fetched from the model schema (public API, no auth required):

ec.fdk describe getEntry --dm 83cc6374 --model muffin
# type MuffinEntry = EntryResourceBase & {
# name: string;
# amazement_factor: number;
# test_asset: string | null;
# color: string;
# }
#
# type EntryResourceBase = {
# id: string;
# _created: Date;
# ...
# }

ec.fdk describe entryList --dm 83cc6374 --model muffin
# type EntryList = {
# count: number;
# total: number;
# items: MuffinEntry[];
# }
# ...

Without --dm/--model, the generic EntryResource type with [key: string]: any is shown.

For resource commands (resourceList, resourceGet, resourceEdit), pass --resource to get the specific typed return:

ec.fdk describe resourceList --resource dm-account
# type DmAccountList = {
# count: number;
# total: number;
# items: DmAccountResource[];
# }
# type DmAccountResource = { accountID: string; email: string | null; ... }

ec.fdk describe resourceGet --resource client
# type ClientResource = { clientID: string; clientName: string; ... }

Note: account and client refer to Account Server resources. Use dm-account and dm-client for DM-level resources (different, smaller types).

Without --resource, the generic ResourceList type is shown.

The -f flag maps directly to entrecode filter query params. Common filter suffixes:

Suffix Meaning Example
~ Search (contains) -f name~=chocolate
From Greater than / after -f createdFrom=2024-01-01
To Less than / before -f createdTo=2025-01-01
, Any of (OR) -f id=id1,id2,id3
! Not null -f active!=
(none) Exact match -f amazement_factor=10

Status/error messages go to stderr, data goes to stdout — so piping always works cleanly.

ec.fdk won't change / decorate data returned from ec APIs. For example, an entry returned from the datamanager will be returned as is. Advantages:

  • The network tab shows what goes into the frontend
  • Resources have the same shape everywhere
  • Resources are serializable

Instead of mutating an EntryResource and calling .save(), we now pass the new value directly:

// this does not exist anymore:
await entry.save(); // <- DONT
// use this to update an entry:
await editEntryObject(entry, value); // <- DO
// alternatively:
await fdk.env(env).dm(dmShortID).model(model).updateEntry(entryID, value);
// or:
await act({ action: 'editEntry', env, dmShortID, model, entryID, value });

Similar to save:

// this does not exist anymore:
await entry.del(); // <- DONT
// use this to delete an entry:
await deleteEntryObject(entry); // <- DO
// alternatively:
await fdk.dm('shortID').model('model').deleteEntry('entryID');
// or:
await act({ action: 'deleteEntry', env, dmShortID, model, entryID });

In ec.fdk, entry asset fields are plain ids:

// assuming "photo" is an asset field:
entry.photo; // <-- this used to be an AssetResource. Now it's a plain id string.
// use this to get the embedded AssetResource:
getEntryAsset('photo', entry); // (no request goes out)
// assuming "lastSeen" is a datetime field:
entry.lastSeen; // <-- this used to be an instance of Date. Now it's a date ISO string
// use this to get a Date instance:
new Date(entry.lastSeen);
// ec.sdk
const api = new PublicAPI(shortID, env, true);
const entryList = await api.entryList(model);
const items = entryList.getAllItems();
const first = entryList.getFirstItem();
// ec.fdk
const api = fdk(env).dm(shortID);
const entryList = await api.entryList(model);
const items = entryList.items; // <------- change
const first = entryList.items[0]; // <------- change
// or in one line:
const entryList = await fdk(env).dm(shortID).entryList(model);

By default, the second param of ec.fdk entryList will just convert the object to url params:

const entryList = await fdk('stage').dm('<shortID>').entryList({ createdTo: '2021-01-18T09:13:47.605Z' });
/*
https://datamanager.cachena.entrecode.de/api/<shortID>/muffin?
_list=true&
createdTo=2021-01-18T09:13:47.605Z&
page=1&
size=50
*/

Read more in the entrecode filtering doc

There is some syntax sugar you can use to get the same behavior as ec.sdk filterOptions:

const entryList = await fdk('stage')
.dm('<shortID>')
.entryList(filterOptions({ created: { to: '2021-01-18T09:13:47.605Z' } }));
// ec.sdk
await api.dmAssetList(group, { assetID: { any: value } });
// ec.fdk
const options = sdkOptions({ assetID: { any: value } } as any);
const assets = await api.assetGroup(group).assetList(options);
// ec.sdk
api.dmAsset(group, img);
// ec.fdk
api.assetGroup(group).getAsset(img);
cd packages/ec.fdk
./publish.sh

The script prompts for a new version, regenerates docs, commits, pushes, and publishes to npm.