The "API" folder Last updated: 05. Aug 2026
Create your custom JSON WEB API by placing JS files that will run server side in the #/API folder.
The #/API folder
Any .js or .hash file placed in this folder will automatically run on the server side and the response from JS files will be converted to JSON and returned.
This means if you put any .js files in this folder they will run server side. Unlike any other folder where they will just be served raw to the front end.
.hash files will execute and return HTML unless a more specific extension such as .css.hash or .css.txt is used. The appropriate MIME type is set automatically by the system based on the extension.
Your first API endpoint
Below is a minimal endpoint you can drop into your workspace right now. Create the file #/API/hello.js:
// #/API/hello.js
export default () => {
return { message: "Hello from Docly API" };
} The endpoint is now available at /API/hello on your site and returns the object above as JSON. Docly serializes the return value for you — no Content-Type or JSON.stringify boilerplate.
To accept input, read from the form object (POSTed form fields) or the query object (querystring). The function below answers with a greeting and validates that the caller supplied a name:
// #/API/greet.js
export default (form) => {
if (!form.name) throw new Error("Missing name parameter");
return { message: "Hello, " + form.name };
} From a hash page or any browser-side script, call it with fetch():
<script>
fetch('/API/greet', {
method: 'POST',
body: new URLSearchParams({ name: 'Anna' })
})
.then(r => r.json())
.then(d => console.log(d.message));
</script> Why keep dynamic logic in #/API/ rather than in a .hash-template? Hash files are cached aggressively by Docly, while API functions run on every request. See Hash files are cached for the full reasoning and the failure mode it avoids.
Responses and errors
Whatever your function returns is serialized to JSON and sent with 200 OK. You control that shape.
A thrown error is different. Docly catches it and replaces your shape with its own envelope — none of your own fields survive:
// return { message: "Hello, Anna" }
200 {"message":"Hello, Anna"}
// throw new Error("Missing name parameter")
403 {"ResultCode":0,"Message":"Missing name parameter"} Two things catch callers out: the field is Message in PascalCase, like the rest of the Docly document format — and the status is 403, not 400. Client code that has only ever seen the success shape tends to look for error or message, find neither, and fall back to a generic "something went wrong" — silently discarding the message the server actually sent.
ResultCode is the machine-readable companion to Message, so the caller can branch on the kind of failure instead of matching on the text. A plain throw new Error(...) leaves it at 0. To set your own, use docly.assert(description, resultCode) — it throws the same envelope with the code you supply, and defaults to 1 when you omit it:
// #/API/greet.js
export default (form) => {
if (!form.name) docly.assert("Missing name parameter", 101);
return { message: "Hello, " + form.name };
}
// 403 {"ResultCode":101,"Message":"Missing name parameter"} The HTTP status stays 403 whichever code you set — ResultCode lives in the body only. Reach for docly.assert when the caller genuinely needs to distinguish failures programmatically; otherwise throw new Error(...) is the simpler and recommended form.
A fetch() caller therefore has to handle both shapes:
const r = await fetch('/API/greet', {
method: 'POST',
body: new URLSearchParams({ name: '' })
});
const d = await r.json();
const msg = d.Message || d.message || d.error; // thrown error vs. your own shape If you would rather have one shape everywhere, return the error instead of throwing it. The response is then 200 with your own object, and you decide the field names:
// #/API/greet.js
export default (form) => {
if (!form.name) return { ok: false, error: "Missing name parameter", code: "validation" };
return { ok: true, message: "Hello, " + form.name };
} The trade-off: throw gives a real 4xx status that logging and monitoring can see, while return always yields 200 and moves the outcome into the body. A common split is to throw for genuine server faults and return for expected user errors, so the status code still means something.
For how input reaches your function in the first place, see Reading query and form fields in API calls.
API functions (read and write)
Because the API functions are not cached on the server an extra set of functions are available when running JS from the API folder. These are here to make it possible to create dynamic web applications that your static frontend (always cached) can access data dynamically from.
The extra functions available include:
Write data to Docly — see Filesystem functions (
saveFile,saveJson, etc.)Get current user information — see Authentication (
writeJwt,getProfilePictureUrl, etc.)Call external APIs — see Network functions
From the API you can read values for querystring from the "query" object and "form" object for posted form values.
See functions marked with API only in the JavaScript Reference.
Security
Docly provides a comprehensive security framework for your API endpoints, including automated threat detection, progressive blocking mechanisms, and built-in protection against common attack vectors. Understanding how authentication and authorization work in Docly is essential for building secure applications.
Docly handles user authentication through its built-in login system. When you publish a folder or website with 'Login Required', Docly ensures that only invited users with valid accounts can access your application. However, you are responsible for implementing authorization logic in your API endpoints to verify that authenticated users have the appropriate permissions for specific operations.
When Docly's security system detects suspicious activity (which you can flag using docly.flagActivity()), it follows a progressive enforcement sequence to protect your API:
The offending IP address is temporarily blocked
To regain access, the user must pass a reCAPTCHA challenge
Continued suspicious activity results in a hard block from your site
Hard-blocked IPs must apply to be unblocked manually
Activity Flagging: Use docly.flagActivity() to flag suspicious activities such as:
Failed login attempts
Invalid API requests
Suspicious access patterns
Unauthorized access attempts
Path Security: All paths in Docly are absolute paths within each webapp. This design eliminates path traversal and injection vulnerabilities, as there is no way to escape the webapp's directory structure.
Authentication vs Authorization: Docly handles authentication - verifying that a user with an invitation to your application is logged in before they can access it. However, you must implement authorization in your API endpoints - checking whether an authenticated user has permission to perform specific actions or access particular data. Unless you're running a public application or implementing a custom login experience, you should maintain your own internal table of users and access rights to control what authenticated users can do within your application.
Caching
API functions are always executed on each call and not cached, unlike .hash files in other folders, which are cached until the underlying data changes and then regenerated on demand on the next request.
For all available functions inside an API endpoint, see the JavaScript Reference. Particularly useful inside an API: Filesystem functions, Network functions, Mail functions, Authentication.