agentsclimarketplace

Rest api development

Skill ColdBox/skills/coldbox/rest-api-development

Collection of skills for the ColdBox Platform and Claude Plugin

Install
npx -y skills add ColdBox/skills --skill rest-api-development

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use this skill when building RESTful APIs in ColdBox using RestHandler, creating CRUD API endpoints, implementing API versioning, handling JWT/bearer token authentication, building structured error responses, or creating resource representations with mementos.

SKILL.md

16.6 KB, ~4.0k tokens by cl100k_base, as published. Nobody here has run it

REST API Development

When to Use This Skill

Use this skill when building REST APIs with ColdBox, including API routing, handlers, authentication, versioning, and response formatting.

Language Mode Reference

Examples use BoxLang (.bx) syntax by default. Adapt for your target language:

ConceptBoxLang (.bx)CFML (.cfc)
Class declarationclass [extends="..."] {component [extends="..."] {
DI annotation@inject above property name="svc";property name="svc" inject="svc";
View templates.bxm suffix.cfm / .cfml suffix
Tag prefix<bx:if>, <bx:output>, <bx:set><cfif>, <cfoutput>, <cfset>

CFML Compat Mode: With BoxLang + CFML Compat module, .bx and .cfc files coexist freely. BoxLang-native classes use class {} (.bx files); CFML-compat classes use component {} (.cfc files).

Core Concepts

ColdBox REST APIs use:

  • RestHandler — base handler with built-in error handling and response formatting
  • event.renderData() — renders JSON/XML/text responses with status codes
  • Route resources — maps CRUD verbs to handler actions
  • Modules — ideal for versioned API isolation
  • JWT/cbSecurity — provides API authentication

API Module Structure

modules_app/
  api/
    config/
      Router.cfc       # API-specific routes
    handlers/
      Users.cfc        # Users API handler
      Posts.cfc
    models/
      UserTransformer.cfc
    ModuleConfig.cfc

RestHandler Base Class

class Users extends coldbox.system.RestHandler {

    @inject
    property name="userService";

    // Runs before every action - great for input validation
    function preHandler( event, rc, prc, action ) {
        // Validate content type for mutation methods
        if( !listFindNoCase( "index,show", action ) ){
            if( !event.isPost() && !event.isPut() && !event.isPatch() ){
                return
            }
            validateContentType( event )
        }
    }

    /**
     * GET /api/v1/users
     */
    function index( event, rc, prc ) {
        var users = userService.list(
            page    = rc.page ?: 1,
            limit   = rc.limit ?: 25,
            filters = getFilters( rc )
        )

        event.renderData(
            data = {
                "data"       : users.getRecords(),
                "pagination" : users.getPagination()
            },
            statusCode = 200
        )
    }

    /**
     * GET /api/v1/users/:id
     */
    function show( event, rc, prc ) {
        var user = userService.getById( rc.id ?: 0 )

        if( isNull( user ) ){
            return apiNotFound( "User not found" )
        }

        event.renderData( data = user.getMemento(), statusCode = 200 )
    }

    /**
     * POST /api/v1/users
     */
    function create( event, rc, prc ) {
        var payload = event.getHTTPContent( json = true )
        var result  = userService.create( payload )

        if( result.hasErrors() ){
            return apiValidationFailed( result.getErrors() )
        }

        event.renderData( data = result.getMemento(), statusCode = 201 )
    }

    /**
     * PUT /api/v1/users/:id
     */
    function update( event, rc, prc ) {
        var payload = event.getHTTPContent( json = true )
        var result  = userService.update( rc.id ?: 0, payload )

        if( isNull( result ) ){
            return apiNotFound( "User not found" )
        }

        if( result.hasErrors() ){
            return apiValidationFailed( result.getErrors() )
        }

        event.renderData( data = result.getMemento(), statusCode = 200 )
    }

    /**
     * DELETE /api/v1/users/:id
     */
    function delete( event, rc, prc ) {
        var deleted = userService.delete( rc.id ?: 0 )

        if( !deleted ){
            return apiNotFound( "User not found" )
        }

        event.renderData( data = "", statusCode = 204 )
    }

    // Private helpers

    private function getFilters( rc ) {
        return {
            search : rc.search ?: "",
            role   : rc.role ?: "",
            active : rc.active ?: ""
        }
    }

    private function apiNotFound( message ) {
        event.renderData(
            data       = { "error": message, "statusCode": 404 },
            statusCode = 404
        )
    }

    private function apiValidationFailed( errors ) {
        event.renderData(
            data = {
                "error"      : "Validation failed",
                "errors"     : errors,
                "statusCode" : 422
            },
            statusCode = 422
        )
    }

    private function validateContentType( event ) {
        if( event.getHTTPHeader( "Content-Type", "" ) does not contain "application/json" ){
            event.renderData(
                data       = { "error": "Content-Type must be application/json" },
                statusCode = 415
            )
            event.noRender( false )
        }
    }
}

CFML (.cfc):

component extends="coldbox.system.RestHandler" {

    property name="userService" inject="userService";

    // Runs before every action - great for input validation
    function preHandler( event, rc, prc, action ) {
        // Validate content type for mutation methods
        if( !listFindNoCase( "index,show", action ) ){
            if( !event.isPost() && !event.isPut() && !event.isPatch() ){
                return
            }
            validateContentType( event )
        }
    }

    /**
     * GET /api/v1/users
     */
    function index( event, rc, prc ) {
        var users = userService.list(
            page    = rc.page ?: 1,
            limit   = rc.limit ?: 25,
            filters = getFilters( rc )
        )

        event.renderData(
            data = {
                "data"       : users.getRecords(),
                "pagination" : users.getPagination()
            },
            statusCode = 200
        )
    }

    /**
     * GET /api/v1/users/:id
     */
    function show( event, rc, prc ) {
        var user = userService.getById( rc.id ?: 0 )

        if( isNull( user ) ){
            return apiNotFound( "User not found" )
        }

        event.renderData( data = user.getMemento(), statusCode = 200 )
    }

    /**
     * POST /api/v1/users
     */
    function create( event, rc, prc ) {
        var payload = event.getHTTPContent( json = true )
        var result  = userService.create( payload )

        if( result.hasErrors() ){
            return apiValidationFailed( result.getErrors() )
        }

        event.renderData( data = result.getMemento(), statusCode = 201 )
    }

    /**
     * PUT /api/v1/users/:id
     */
    function update( event, rc, prc ) {
        var payload = event.getHTTPContent( json = true )
        var result  = userService.update( rc.id ?: 0, payload )

        if( isNull( result ) ){
            return apiNotFound( "User not found" )
        }

        if( result.hasErrors() ){
            return apiValidationFailed( result.getErrors() )
        }

        event.renderData( data = result.getMemento(), statusCode = 200 )
    }

    /**
     * DELETE /api/v1/users/:id
     */
    function delete( event, rc, prc ) {
        var deleted = userService.delete( rc.id ?: 0 )

        if( !deleted ){
            return apiNotFound( "User not found" )
        }

        event.renderData( data = "", statusCode = 204 )
    }

    // Private helpers

    private function getFilters( rc ) {
        return {
            search : rc.search ?: "",
            role   : rc.role ?: "",
            active : rc.active ?: ""
        }
    }

    private function apiNotFound( message ) {
        event.renderData(
            data       = { "error": message, "statusCode": 404 },
            statusCode = 404
        )
    }

    private function apiValidationFailed( errors ) {
        event.renderData(
            data = {
                "error"      : "Validation failed",
                "errors"     : errors,
                "statusCode" : 422
            },
            statusCode = 422
        )
    }

    private function validateContentType( event ) {
        if( event.getHTTPHeader( "Content-Type", "" ) does not contain "application/json" ){
            event.renderData(
                data       = { "error": "Content-Type must be application/json" },
                statusCode = 415
            )
            event.noRender( false )
        }
    }
}

API Module Configuration

// modules_app/api/ModuleConfig.cfc
class ModuleConfig {

    property name="title"       default="API Module";
    property name="description" default="REST API";
    property name="version"     default="1.0.0";
    property name="entryPoint"  default="api";
    property name="author"      default="Ortus Solutions";

    function configure() {
        // Module settings
        settings = {
            jwtSecret     : getSystemSetting( "JWT_SECRET", "" ),
            tokenExpiry   : 60, // minutes
            refreshExpiry : 10080 // 7 days
        }
    }
}

// modules_app/api/config/Router.cfc
class Router extends coldbox.system.web.routing.Router {

    function configure() {
        // V1 resource routes
        group( pattern = "/v1" ) {
            resources( "users" )
            resources( "posts" )
        }
    }
}

CFML (.cfc):

// modules_app/api/ModuleConfig.cfc
component {

    property name="title"       default="API Module";
    property name="description" default="REST API";
    property name="version"     default="1.0.0";
    property name="entryPoint"  default="api";
    property name="author"      default="Ortus Solutions";

    function configure() {
        // Module settings
        settings = {
            jwtSecret     : getSystemSetting( "JWT_SECRET", "" ),
            tokenExpiry   : 60, // minutes
            refreshExpiry : 10080 // 7 days
        }
    }
}

// modules_app/api/config/Router.cfc
component extends="coldbox.system.web.routing.Router" {

    function configure() {
        // V1 resource routes
        group( pattern = "/v1" ) {
            resources( "users" )
            resources( "posts" )
        }
    }
}

JWT Authentication

class Auth extends coldbox.system.RestHandler {

    @inject
    property name="authService";

    @inject
    property name="jwtService";

    /**
     * POST /api/v1/auth/login
     */
    function login( event, rc, prc ) {
        var credentials = event.getHTTPContent( json = true )

        if( !authService.authenticate( credentials.email, credentials.password ) ){
            event.renderData(
                data       = { "error": "Invalid credentials" },
                statusCode = 401
            )
            return
        }

        var user  = authService.getAuthenticatedUser()
        var token = jwtService.fromUser( user )

        event.renderData(
            data = {
                "access_token"  : token,
                "token_type"    : "Bearer",
                "expires_in"    : 3600,
                "user"          : user.getMemento()
            },
            statusCode = 200
        )
    }

    /**
     * POST /api/v1/auth/refresh
     */
    function refresh( event, rc, prc ) {
        var bearer = getBearerToken( event )
        var token  = jwtService.refreshToken( bearer )

        event.renderData(
            data = { "access_token": token, "token_type": "Bearer" },
            statusCode = 200
        )
    }

    /**
     * POST /api/v1/auth/logout
     */
    function logout( event, rc, prc ) {
        var bearer = getBearerToken( event )
        jwtService.invalidateToken( bearer )
        event.renderData( data = { "message": "Logged out" }, statusCode = 200 )
    }

    private function getBearerToken( event ) {
        var header = event.getHTTPHeader( "Authorization", "" )
        return replaceNoCase( header, "Bearer ", "", "one" )
    }
}

CFML (.cfc):

component extends="coldbox.system.RestHandler" {

    property name="authService" inject="authService";

    property name="jwtService" inject="jwtService";

    /**
     * POST /api/v1/auth/login
     */
    function login( event, rc, prc ) {
        var credentials = event.getHTTPContent( json = true )

        if( !authService.authenticate( credentials.email, credentials.password ) ){
            event.renderData(
                data       = { "error": "Invalid credentials" },
                statusCode = 401
            )
            return
        }

        var user  = authService.getAuthenticatedUser()
        var token = jwtService.fromUser( user )

        event.renderData(
            data = {
                "access_token"  : token,
                "token_type"    : "Bearer",
                "expires_in"    : 3600,
                "user"          : user.getMemento()
            },
            statusCode = 200
        )
    }

    /**
     * POST /api/v1/auth/refresh
     */
    function refresh( event, rc, prc ) {
        var bearer = getBearerToken( event )
        var token  = jwtService.refreshToken( bearer )

        event.renderData(
            data = { "access_token": token, "token_type": "Bearer" },
            statusCode = 200
        )
    }

    /**
     * POST /api/v1/auth/logout
     */
    function logout( event, rc, prc ) {
        var bearer = getBearerToken( event )
        jwtService.invalidateToken( bearer )
        event.renderData( data = { "message": "Logged out" }, statusCode = 200 )
    }

    private function getBearerToken( event ) {
        var header = event.getHTTPHeader( "Authorization", "" )
        return replaceNoCase( header, "Bearer ", "", "one" )
    }
}

API Versioning

// Using modules for versioning
// modules_app/apiV1/ModuleConfig.cfc
class ModuleConfig {
    property name="entryPoint" default="api/v1";
}

// modules_app/apiV2/ModuleConfig.cfc
class ModuleConfig {
    property name="entryPoint" default="api/v2";
}

// URL-based versioning
group( pattern = "/api/v1" ) {
    resources( "users" )
}
group( pattern = "/api/v2" ) {
    resources( "users" )
}

// Header-based versioning
function preHandler( event, rc, prc, action ) {
    var version = event.getHTTPHeader( "Accept-Version", "v1" )
    prc.apiVersion = version
}

CFML (.cfc):

// Using modules for versioning
// modules_app/apiV1/ModuleConfig.cfc
component {
    property name="entryPoint" default="api/v1";
}

// modules_app/apiV2/ModuleConfig.cfc
component {
    property name="entryPoint" default="api/v2";
}

// URL-based versioning
group( pattern = "/api/v1" ) {
    resources( "users" )
}
group( pattern = "/api/v2" ) {
    resources( "users" )
}

// Header-based versioning
function preHandler( event, rc, prc, action ) {
    var version = event.getHTTPHeader( "Accept-Version", "v1" )
    prc.apiVersion = version
}

Pagination Response Structure

function index( event, rc, prc ) {
    var page  = rc.page ?: 1
    var limit = min( rc.limit ?: 25, 100 )   // cap at 100

    var results = userService.paginate(
        offset = ( page - 1 ) * limit,
        limit  = limit
    )

    event.renderData(
        data = {
            "data"  : results.records,
            "meta"  : {
                "total"       : results.total,
                "page"        : page,
                "limit"       : limit,
                "total_pages" : ceiling( results.total / limit ),
                "has_more"    : ( page * limit ) < results.total
            },
            "links" : {
                "self"  : buildLink( event.getCurrentEvent(), { page: page, limit: limit } ),
                "next"  : ( page * limit ) < results.total ? buildLink( event.getCurrentEvent(), { page: page + 1, limit: limit } ) : "",
                "prev"  : page > 1 ? buildLink( event.getCurrentEvent(), { page: page - 1, limit: limit } ) : ""
            }
        },
        statusCode = 200
    )
}

REST API Best Practices

  • Use RestHandler over EventHandler for APIs — it adds proper error handling
  • Use HTTP status codes correctly (200, 201, 204, 400, 401, 403, 404, 422, 500)
  • Read request body with event.getHTTPContent( json = true ) for POST/PUT
  • Structure responses consistently — always return data + optional meta + optional links
  • Use modules for API versioning to keep versions independent
  • Validate inputs before processing — return 422 with errors structure
  • Secure endpoints with JWT + cbSecurity @secured annotations
  • Use CORS interceptors for cross-origin access

Gives 0 of the 12 instructions most apis services skills give in ~4.0k tokens

Counted across 424 of the 426 authors here whose files we hold, read 2026-08-06

  • use plural nouns for resource namesin 41 of 424, across 32 files
  • use cursor-based pagination for large datasetsin 35 of 424, across 20 files
  • include rate limit headers in responsesin 25 of 424, across 13 files
  • Use kebab-case for multi-word resourcesin 23 of 424, across 13 files
  • version APIs in the URL pathin 19 of 424, across 9 files
  • use semantic HTTP status codesin 18 of 424, across 8 files
  • verify webhook signaturesin 18 of 424, across 11 files
  • use query parameters for filteringin 17 of 424, across 6 files
  • use async database operationsin 14 of 424, across 7 files
  • wrap successful responses in a data fieldin 13 of 424, across 3 files
  • prefix sorting parameters with a hyphen for descending orderin 13 of 424, across 3 files
  • set appropriate HTTP status codesin 13 of 424, across 6 files

Said here and by no other author read

  • extend the RestHandler base class
  • render responses using event.renderData()
  • map CRUD verbs to resource routes
  • isolate API versions inside modules
  • validate content type for mutation methods
  • use preHandler for input validation

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.