From celigo
Build Celigo APIs -- custom HTTP endpoints that let external systems push or query data synchronously through Celigo integrations. Use when creating APIs, proxying authenticated requests, or exposing lookup/write operations as a REST interface that returns a structured response.
How this skill is triggered — by the user, by Claude, or both
Slash command
/celigo:building-apisThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
<!-- TIER:1 -->
references/schemas/api-request.ymlreferences/schemas/api-response.ymlreferences/schemas/apim.ymlreferences/schemas/builder.ymlreferences/schemas/request.ymlreferences/schemas/response-router.ymlreferences/schemas/response.ymlreferences/schemas/router.ymlreferences/schemas/script.ymlreferences/schemas/shipworks.ymlAn API is a RESTful endpoint that exposes integration logic for external consumption. External systems call the API over HTTP; the API processes the request through lookups and imports, then returns a structured response. Concerns when building an API:
pageProcessors[] entry, same as in flows. For lookup exports the response has data[] and errors[] (use data[0].fieldName for single results). For imports the response is via _json (use _json.fieldName)pageProcessors[] entriesUsed across integrations alongside flows and tools. APIs do not have their own authentication -- incoming requests authenticate via the Celigo API token; outbound calls to external systems use the connections referenced by exports/imports in the pipeline.
type: "builder")Visual configuration with discrete components:
API (type: "builder")
+-- request -- method, relativeURI, params, bodySchema, mockRequest, transform
+-- routers[] -- processing pipeline (same structure as flow routers)
| +-- branches[]
| +-- inputFilter -- when to use this branch (s-expression rules)
| +-- pageProcessors[] -- lookups (exports) and imports
| +-- nextRouterId -- chain to next router, or "apiRouter" to finish
+-- responseRouter -- id="apiRouter", routes processed data to a response
+-- responses[] -- success, fail, custom -- each with statusCode, inputFilter, mappings
The incoming HTTP request replaces the export as data source. Routers and page processors work identically to flows.
When an API receives a request:
routeRecordsUsing evaluates branch input filter conditionsresponseMapping on each processor carries data forward to the next processorresponseRouter (id="apiRouter") selects which response template to use based on response input filterstype: "script")A single handleRequest JavaScript function receives the request object (method, headers, queryParams, body, pathParams) and returns {statusCode, headers, body}. Complete control with no visual configuration.
Legacy APIs (no type field, top-level _scriptId + function) exist in production but are not represented in the current spec. Distinguish by: if type is absent/null and _scriptId is present, it's legacy.
| Scenario | Mode | Why |
|---|---|---|
| Standard lookup/write with structured response | Builder | Visual debugging, test runs, structured responses |
| Multiple response shapes based on success/failure | Builder | Response router + inputFilter handles this declaratively |
| Complex conditional logic or custom auth validation | Script | Full JavaScript control over request/response |
| Dynamic routing that can't be expressed as input filters | Script | handleRequest can implement arbitrary logic |
| Proxy through an authenticated connection | Builder | Wire the connection's export/import as a page processor |
| Simple webhook receiver that transforms and forwards | Builder | Single router, single branch, one import |
| Mode | Required Fields |
|---|---|
| Builder | name, type: "builder", builder.request (method + relativeURI) |
| Script | name, type: "script", script._scriptId, script.function |
| Legacy | name, _scriptId, function (no type field) |
All schemas are in references/schemas/:
| Schema | What it defines |
|---|---|
| request.yml | Top-level API fields (name, type, version, disabled, builder/script refs) |
| response.yml | API response shape |
| builder.yml | Builder configuration (request, routers, responseRouter, responses refs) |
| api-request.yml | Request config (method, relativeURI, params, bodySchema, mockRequest, transform) |
| api-response.yml | Response definitions (id, name, type, statusCode, inputFilter, mappings, hooks) |
| response-router.yml | Response router (id="apiRouter", routeRecordsUsing) |
| router.yml | Routers (branches, inputFilter, pageProcessors) |
| script.yml | Script config (_scriptId, function) |
| apim.yml | APIM metadata (publication status) |
| shipworks.yml | Legacy ShipWorks auth |
handleRequest (script-mode APIs), preMap/postMap hooks, and postResponseMapBefore creating anything, understand the requirements: what endpoint the caller needs, what data it sends, what systems are involved, what the response should look like. This determines everything -- mode, pipeline shape, which connections/exports/imports are needed.
Use builder for most APIs -- it provides visual debugging, test runs, and structured responses. Use script only when the processing logic is too dynamic for the visual pipeline (e.g., complex conditional responses, custom auth validation, dynamic routing).
Look for connections, exports, and imports that can be reused before creating new ones.
# Search across all resource types in the account
celigo account search "<keyword>"
# Show what an existing API uses (exports, imports, connections)
celigo account deps api <id>
# Find orphaned resources that could be reused
celigo account lint
# Search for APIs already in the account for patterns
celigo apis list | grep -i "<keyword>"
# Check existing exports/imports that could serve as pipeline steps
celigo exports list | grep -i "<system-name>"
celigo imports list | grep -i "<system-name>"
# Search marketplace for pre-built integration templates
celigo templates search "<application-name>"
The account index auto-refreshes when stale (>4 hours). Force a fresh snapshot with celigo account snapshot.
APIs reference exports and imports as page processors -- these must exist before you can attach them. Build order:
configuring-exports skill)configuring-imports skill)Choose the HTTP method and URI path. GET and POST are most common; PUT and PATCH are rare.
/customers/:idmockRequest for testing the pipeline without live callstransform (expression-based or script-based) to reshape incoming data before processingThe pipeline is made of routers, branches, and page processors. See router.yml for the full schema.
Every builder API needs at least one router -- it's the container that holds branches, and branches hold the page processors that do the actual work. Use multiple branches when different request conditions need different processing paths (e.g., branch by HTTP method, request field value, or record type). Use multiple routers when you need sequential stages of processing where each stage can branch independently.
For pass-through routers (single branch, no filters, just linear steps before a branching router), omit routeRecordsTo and routeRecordsUsing -- including them makes it appear as a filter-based branch in the UI. The API defaults are sufficient.
Input filters use s-expression syntax: ["operator", ["type", ["extract", "field"]], value]. Type wrappers (string, number, boolean) are required around extract and context accessors. Logical combinators: ["and", cond1, cond2], ["or", cond1, cond2].
The last branch in the chain must set nextRouterId: "apiRouter" to reach the response router.
Every builder API needs exactly one success response and one fail response. Add custom responses for specific scenarios (e.g., 404 not found, 422 validation error).
Each response has:
statusCode (HTTP status code)inputFilter to determine when it's selected (typically ["equals", ["boolean", ["context", "success"]], true] for success)mappings to shape the response body from the processed recordbodySchema for documentation, headers, lookups, and hooks (preMap, postMap)Set id: "apiRouter" and choose routing method:
input_filters (default) -- evaluates each response's inputFilterscript -- custom JavaScript returns the response id to useReference the Schema Index above for exact field schemas.
Every API needs at minimum: name, type, and either builder (with request) or script (with _scriptId and function).
# CRUD
celigo apis list
celigo apis get <id>
celigo apis create < api.json
celigo apis update <id> < api.json
celigo apis set <id> key=value [key2=value2 ...]
celigo apis delete <id>
# Clone (builder-mode only)
celigo apis clone <id> --api-version <version> [--name <name>] [--description <desc>] [--environment <envId>]
# Pipeline management
celigo apis add-processor <id> <exportOrImportId> [--router <routerId>] [--branch <branchName>]
celigo apis remove-processor <id> <exportOrImportId> [--router <routerId>] [--branch <branchName>]
# Logs
celigo apis logs <id>
celigo apis log-detail <id> <key>
# Test run
celigo apis test-run <id>
celigo apis test-run-step <id> <runId> <exportOrImportId>
celigo apis test-run-step-logs <id> <runId> <exportOrImportId>
# Debug (for exports/imports within the API pipeline)
celigo apis debug-requests <id> <exportOrImportId> [--since <minutes>]
celigo apis debug-request-detail <id> <exportOrImportId> <key>
# Discovery
celigo account search "<keyword>"
celigo templates search "<name>"
Before creating or updating an API, verify:
name is set and descriptivetype is "builder" or "script" (not omitted, which creates a legacy API)builder.request.method and builder.request.relativeURI are setnextRouterId: "apiRouter"success and fail responses are definedinputFilter uses ["equals", ["boolean", ["context", "success"]], true]script._scriptId and script.function reference a valid script_exportId and _importId references in page processors point to existing resourcesversion is set (it becomes part of the endpoint URL: /{version}{relativeURI})extract/context accessors in type wrappers (string, number, boolean)set command handles this automatically.first_matching_branch routing. Unlike flows which also support all_matching_branches, API routers always stop at the first matching branch.inputFilter type wrappers silently fails. Use ["boolean", ["context", "success"]], not bare ["context", "success"] -- the filter will never match without the wrapper.**version becomes part of the URL path.** The full endpoint is /{version}{relativeURI}. Changing the version changes the URL that callers must use.| Error | Cause | Fix |
|---|---|---|
404 on API endpoint | Wrong version or relativeURI in the request | Verify the full URL is /{version}{relativeURI} and both match the API definition |
422 validation error on create/update | Missing required fields or invalid field values | Check the Pre-Submit Checklist; verify type is set |
Response always returns the fail response | Success inputFilter is malformed or missing type wrapper | Use ["equals", ["boolean", ["context", "success"]], true] exactly |
| Response body is empty | Response mappings not configured or field paths don't match | Verify mapping extract paths match the actual processed record structure |
| Pipeline step silently skipped | inputFilter on a branch evaluates to false for all records | Debug with celigo apis test-run-step to see each step's input/output |
Clone failed error | Attempting to clone a script-mode or legacy API | Clone is builder-mode only; recreate script APIs manually |
| Page processor returns no data | Export/import _id reference is wrong or resource is disabled | Verify the referenced resource exists and is enabled with celigo exports get / celigo imports get |
Router ID not found error | nextRouterId references a non-existent router ID | Ensure all nextRouterId values match a real router id or "apiRouter" |
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub celigo/ai --plugin celigo